1/****************************************************************************
2**
3** Copyright (C) 2016 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#include <assimp/Importer.hpp>
30#include <assimp/IOStream.hpp>
31#include <assimp/IOSystem.hpp>
32#include <assimp/scene.h>
33#include <assimp/postprocess.h>
34
35#include <qiodevice.h>
36#include <qfile.h>
37#include <qfileinfo.h>
38#include <qdir.h>
39#include <qhash.h>
40#include <qdebug.h>
41#include <qcoreapplication.h>
42#include <qcommandlineparser.h>
43#include <qjsondocument.h>
44#include <qjsonobject.h>
45#include <qjsonarray.h>
46#include <qmath.h>
47
48#define GLT_UNSIGNED_SHORT 0x1403
49#define GLT_UNSIGNED_INT 0x1405
50#define GLT_FLOAT 0x1406
51
52#define GLT_FLOAT_VEC2 0x8B50
53#define GLT_FLOAT_VEC3 0x8B51
54#define GLT_FLOAT_VEC4 0x8B52
55#define GLT_FLOAT_MAT3 0x8B5B
56#define GLT_FLOAT_MAT4 0x8B5C
57#define GLT_SAMPLER_2D 0x8B5E
58
59#define GLT_ARRAY_BUFFER 0x8892
60#define GLT_ELEMENT_ARRAY_BUFFER 0x8893
61
62#define GLT_DEPTH_TEST 0x0B71
63#define GLT_CULL_FACE 0x0B44
64#define GLT_BLEND 0x0BE2
65
66class AssimpIOStream : public Assimp::IOStream
67{
68public:
69 AssimpIOStream(QIODevice *device);
70 ~AssimpIOStream();
71
72 size_t Read(void *pvBuffer, size_t pSize, size_t pCount) override;
73 size_t Write(const void *pvBuffer, size_t pSize, size_t pCount) override;
74 aiReturn Seek(size_t pOffset, aiOrigin pOrigin) override;
75 size_t Tell() const override;
76 size_t FileSize() const override;
77 void Flush() override;
78
79private:
80 QIODevice *m_device;
81};
82
83class AssimpIOSystem : public Assimp::IOSystem
84{
85public:
86 bool Exists(const char *pFile) const override;
87 char getOsSeparator() const override;
88 Assimp::IOStream *Open(const char *pFile, const char *pMode) override;
89 void Close(Assimp::IOStream *pFile) override;
90};
91
92AssimpIOStream::AssimpIOStream(QIODevice *device) :
93 m_device(device)
94{
95 Q_ASSERT(m_device);
96}
97
98AssimpIOStream::~AssimpIOStream()
99{
100 delete m_device;
101}
102
103size_t AssimpIOStream::Read(void *pvBuffer, size_t pSize, size_t pCount)
104{
105 qint64 readBytes = m_device->read(data: (char *)pvBuffer, maxlen: pSize * pCount);
106 if (readBytes < 0)
107 qWarning() << Q_FUNC_INFO << " read failed";
108 return readBytes;
109}
110
111size_t AssimpIOStream::Write(const void *pvBuffer, size_t pSize, size_t pCount)
112{
113 qint64 writtenBytes = m_device->write(data: (char *)pvBuffer, len: pSize * pCount);
114 if (writtenBytes < 0)
115 qWarning() << Q_FUNC_INFO << " write failed";
116 return writtenBytes;
117}
118
119aiReturn AssimpIOStream::Seek(size_t pOffset, aiOrigin pOrigin)
120{
121 qint64 seekPos = pOffset;
122
123 if (pOrigin == aiOrigin_CUR)
124 seekPos += m_device->pos();
125 else if (pOrigin == aiOrigin_END)
126 seekPos += m_device->size();
127
128 if (!m_device->seek(pos: seekPos)) {
129 qWarning() << Q_FUNC_INFO << " seek failed";
130 return aiReturn_FAILURE;
131 }
132 return aiReturn_SUCCESS;
133}
134
135size_t AssimpIOStream::Tell() const
136{
137 return m_device->pos();
138}
139
140size_t AssimpIOStream::FileSize() const
141{
142 return m_device->size();
143}
144
145void AssimpIOStream::Flush()
146{
147 // we don't write via assimp
148}
149
150static QIODevice::OpenMode openModeFromText(const char *name) noexcept
151{
152 static const struct OpenModeMapping {
153 char name[2];
154 int mode;
155 } openModeMapping[] = {
156 { .name: { 'r', 0 }, .mode: QIODevice::ReadOnly },
157 { .name: { 'r', '+' }, .mode: QIODevice::ReadWrite },
158 { .name: { 'w', 0 }, .mode: QIODevice::WriteOnly | QIODevice::Truncate },
159 { .name: { 'w', '+' }, .mode: QIODevice::ReadWrite | QIODevice::Truncate },
160 { .name: { 'a', 0 }, .mode: QIODevice::WriteOnly | QIODevice::Append },
161 { .name: { 'a', '+' }, .mode: QIODevice::ReadWrite | QIODevice::Append },
162 { .name: { 'w', 'b' }, .mode: QIODevice::WriteOnly },
163 { .name: { 'w', 't' }, .mode: QIODevice::WriteOnly | QIODevice::Text },
164 { .name: { 'r', 'b' }, .mode: QIODevice::ReadOnly },
165 { .name: { 'r', 't' }, .mode: QIODevice::ReadOnly | QIODevice::Text },
166 };
167
168 for (auto e : openModeMapping) {
169 if (qstrncmp(str1: e.name, str2: name, len: sizeof(OpenModeMapping::name)) == 0)
170 return static_cast<QIODevice::OpenMode>(e.mode);
171 }
172 return QIODevice::NotOpen;
173}
174
175bool AssimpIOSystem::Exists(const char *pFile) const
176{
177 return QFileInfo::exists(file: QString::fromUtf8(str: pFile));
178}
179
180char AssimpIOSystem::getOsSeparator() const
181{
182 return QDir::separator().toLatin1();
183}
184
185Assimp::IOStream *AssimpIOSystem::Open(const char *pFile, const char *pMode)
186{
187 const QString fileName(QString::fromUtf8(str: pFile));
188 const QLatin1String cleanedMode = QLatin1String{pMode}.trimmed();
189
190 if (const QIODevice::OpenMode openMode = openModeFromText(name: cleanedMode.data())) {
191 QScopedPointer<QFile> file(new QFile(fileName));
192 if (file->open(flags: openMode))
193 return new AssimpIOStream(file.take());
194 }
195
196 return nullptr;
197}
198
199void AssimpIOSystem::Close(Assimp::IOStream *pFile)
200{
201 delete pFile;
202}
203
204static inline QString ai2qt(const aiString &str)
205{
206 return QString::fromUtf8(str: str.data, size: int(str.length));
207}
208
209static inline QVector<float> ai2qt(const aiMatrix4x4 &matrix)
210{
211 return QVector<float>() << matrix.a1 << matrix.b1 << matrix.c1 << matrix.d1
212 << matrix.a2 << matrix.b2 << matrix.c2 << matrix.d2
213 << matrix.a3 << matrix.b3 << matrix.c3 << matrix.d3
214 << matrix.a4 << matrix.b4 << matrix.c4 << matrix.d4;
215}
216
217struct Options {
218 QString outDir;
219#ifndef QT_BOOTSTRAPPED
220 bool genBin;
221#endif
222 bool compact;
223 bool compress;
224 bool genTangents;
225 bool interleave;
226 float scale;
227 bool genCore;
228 enum TextureCompression {
229 NoTextureCompression,
230 ETC1
231 };
232 TextureCompression texComp;
233 bool commonMat;
234 bool shaders;
235 bool showLog;
236} opts;
237
238class Importer
239{
240public:
241 Importer();
242 virtual ~Importer();
243
244 virtual bool load(const QString &filename) = 0;
245
246 struct BufferInfo {
247 QString name;
248 QByteArray data;
249 };
250 QVector<BufferInfo> buffers() const;
251
252 struct MeshInfo {
253 struct BufferView {
254 BufferView() : bufIndex(0), offset(0), length(0), componentType(0), target(0) { }
255 QString name;
256 uint bufIndex;
257 uint offset;
258 uint length;
259 uint componentType;
260 uint target;
261 };
262 QVector<BufferView> views;
263 struct Accessor {
264 Accessor() : offset(0), stride(0), count(0), componentType(0) { }
265 QString name;
266 QString usage;
267 QString bufferView;
268 uint offset;
269 uint stride;
270 uint count;
271 uint componentType;
272 QString type;
273 QVector<float> minVal;
274 QVector<float> maxVal;
275 };
276 QVector<Accessor> accessors;
277 QString name; // generated
278 QString originalName; // may be empty
279 uint materialIndex;
280 };
281
282 QVector<MeshInfo::BufferView> bufferViews() const;
283 QVector<MeshInfo::Accessor> accessors() const;
284 uint meshCount() const;
285 MeshInfo meshInfo(uint meshIndex) const;
286
287 struct MaterialInfo {
288 QString name;
289 QString originalName;
290 QHash<QByteArray, QVector<float> > m_colors;
291 QHash<QByteArray, float> m_values;
292 QHash<QByteArray, QString> m_textures;
293 };
294 uint materialCount() const;
295 MaterialInfo materialInfo(uint materialIndex) const;
296
297 QSet<QString> externalTextures() const;
298
299 struct CameraInfo {
300 QString name; // suffixed
301 float aspectRatio;
302 float yfov;
303 float zfar;
304 float znear;
305 };
306 QHash<QString, CameraInfo> cameraInfo() const;
307
308 struct EmbeddedTextureInfo {
309 EmbeddedTextureInfo() { }
310 QString name;
311#ifdef HAS_QIMAGE
312 EmbeddedTextureInfo(const QString &name, const QImage &image) : name(name), image(image) { }
313 QImage image;
314#endif
315 };
316 QHash<QString, EmbeddedTextureInfo> embeddedTextures() const;
317
318 struct Node {
319 QString name;
320 QString uniqueName; // generated
321 QVector<float> transformation;
322 QVector<Node *> children;
323 QVector<uint> meshes;
324 };
325 const Node *rootNode() const;
326
327 struct KeyFrame {
328 KeyFrame() : t(0), transValid(false), rotValid(false), scaleValid(false) { }
329 float t;
330 bool transValid;
331 QVector<float> trans;
332 bool rotValid;
333 QVector<float> rot;
334 bool scaleValid;
335 QVector<float> scale;
336 };
337 struct AnimationInfo {
338 AnimationInfo() : hasTranslation(false), hasRotation(false), hasScale(false) { }
339 QString name;
340 QString targetNode;
341 bool hasTranslation;
342 bool hasRotation;
343 bool hasScale;
344 QVector<KeyFrame> keyFrames;
345 };
346 QVector<AnimationInfo> animations() const;
347
348 bool allMeshesForMaterialHaveTangents(uint materialIndex) const;
349
350 const Node *findNode(const Node *root, const QString &originalName) const;
351
352protected:
353 void delNode(Importer::Node *n);
354
355 QByteArray m_buffer;
356 QHash<uint, MeshInfo> m_meshInfo;
357 QHash<uint, MaterialInfo> m_materialInfo;
358 QHash<QString, EmbeddedTextureInfo> m_embeddedTextures;
359 QSet<QString> m_externalTextures;
360 QHash<QString, CameraInfo> m_cameraInfo;
361 Node *m_rootNode;
362 QVector<AnimationInfo> m_animations;
363};
364QT_BEGIN_NAMESPACE
365Q_DECLARE_TYPEINFO(Importer::BufferInfo, Q_MOVABLE_TYPE);
366Q_DECLARE_TYPEINFO(Importer::MeshInfo::BufferView, Q_MOVABLE_TYPE);
367Q_DECLARE_TYPEINFO(Importer::MeshInfo::Accessor, Q_MOVABLE_TYPE);
368Q_DECLARE_TYPEINFO(Importer::MaterialInfo, Q_MOVABLE_TYPE);
369Q_DECLARE_TYPEINFO(Importer::CameraInfo, Q_MOVABLE_TYPE);
370Q_DECLARE_TYPEINFO(Importer::EmbeddedTextureInfo, Q_MOVABLE_TYPE);
371Q_DECLARE_TYPEINFO(Importer::Node, Q_COMPLEX_TYPE); // uses address as identity
372Q_DECLARE_TYPEINFO(Importer::KeyFrame, Q_MOVABLE_TYPE);
373Q_DECLARE_TYPEINFO(Importer::AnimationInfo, Q_MOVABLE_TYPE);
374QT_END_NAMESPACE
375
376Importer::Importer()
377 : m_rootNode(nullptr)
378{
379}
380
381void Importer::delNode(Importer::Node *n)
382{
383 if (!n)
384 return;
385 for (Importer::Node *c : qAsConst(t&: n->children))
386 delNode(n: c);
387 delete n;
388}
389
390Importer::~Importer()
391{
392 delNode(n: m_rootNode);
393}
394
395QVector<Importer::BufferInfo> Importer::buffers() const
396{
397 BufferInfo b;
398 b.name = QStringLiteral("buf");
399 b.data = m_buffer;
400 return QVector<BufferInfo>() << b;
401}
402
403const Importer::Node *Importer::rootNode() const
404{
405 return m_rootNode;
406}
407
408bool Importer::allMeshesForMaterialHaveTangents(uint materialIndex) const
409{
410 for (const MeshInfo &mi : m_meshInfo) {
411 if (mi.materialIndex == materialIndex) {
412 bool hasTangents = false;
413 for (const MeshInfo::Accessor &acc : mi.accessors) {
414 if (acc.usage == QStringLiteral("TANGENT")) {
415 hasTangents = true;
416 break;
417 }
418 }
419 if (!hasTangents)
420 return false;
421 }
422 }
423 return true;
424}
425
426QVector<Importer::MeshInfo::BufferView> Importer::bufferViews() const
427{
428 QVector<Importer::MeshInfo::BufferView> bv;
429 for (const MeshInfo &mi : m_meshInfo) {
430 for (const MeshInfo::BufferView &v : mi.views)
431 bv << v;
432 }
433 return bv;
434}
435
436QVector<Importer::MeshInfo::Accessor> Importer::accessors() const
437{
438 QVector<Importer::MeshInfo::Accessor> acc;
439 for (const MeshInfo &mi : m_meshInfo) {
440 for (const MeshInfo::Accessor &a : mi.accessors)
441 acc << a;
442 }
443 return acc;
444}
445
446uint Importer::meshCount() const
447{
448 return m_meshInfo.count();
449}
450
451Importer::MeshInfo Importer::meshInfo(uint meshIndex) const
452{
453 return m_meshInfo[meshIndex];
454}
455
456uint Importer::materialCount() const
457{
458 return m_materialInfo.count();
459}
460
461Importer::MaterialInfo Importer::materialInfo(uint materialIndex) const
462{
463 return m_materialInfo[materialIndex];
464}
465
466QHash<QString, Importer::CameraInfo> Importer::cameraInfo() const
467{
468 return m_cameraInfo;
469}
470
471QSet<QString> Importer::externalTextures() const
472{
473 return m_externalTextures;
474}
475
476QHash<QString, Importer::EmbeddedTextureInfo> Importer::embeddedTextures() const
477{
478 return m_embeddedTextures;
479}
480
481QVector<Importer::AnimationInfo> Importer::animations() const
482{
483 return m_animations;
484}
485
486const Importer::Node *Importer::findNode(const Node *root, const QString &originalName) const
487{
488 for (const Node *c : root->children) {
489 if (c->name == originalName)
490 return c;
491 const Node *cn = findNode(root: c, originalName);
492 if (cn)
493 return cn;
494 }
495 return nullptr;
496}
497
498class AssimpImporter : public Importer
499{
500public:
501 AssimpImporter();
502
503 bool load(const QString &filename) override;
504
505private:
506 const aiScene *scene() const;
507 void printNodes(const aiNode *node, int level = 1);
508 void buildBuffer();
509 void parseEmbeddedTextures();
510 void parseMaterials();
511 void parseCameras();
512 void parseNode(Importer::Node *dst, const aiNode *src);
513 void parseScene();
514 void parseAnimations();
515 void addKeyFrame(QVector<KeyFrame> &keyFrames, float t, aiVector3D *vt, aiQuaternion *vr, aiVector3D *vs);
516
517 QScopedPointer<Assimp::Importer> m_importer;
518};
519
520AssimpImporter::AssimpImporter() :
521 m_importer(new Assimp::Importer)
522{
523 m_importer->SetIOHandler(new AssimpIOSystem);
524 m_importer->SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, iValue: aiPrimitiveType_LINE | aiPrimitiveType_POINT);
525}
526
527bool AssimpImporter::load(const QString &filename)
528{
529 uint flags = aiProcess_Triangulate | aiProcess_SortByPType
530 | aiProcess_JoinIdenticalVertices
531 | aiProcess_GenSmoothNormals
532 | aiProcess_GenUVCoords
533 | aiProcess_FlipUVs
534 | aiProcess_FindDegenerates;
535
536 if (opts.genTangents)
537 flags |= aiProcess_CalcTangentSpace;
538
539 const aiScene *scene = m_importer->ReadFile(pFile: filename.toUtf8().constData(), pFlags: flags);
540 if (!scene)
541 return false;
542
543 if (opts.showLog) {
544 qDebug().noquote() << filename
545 << scene->mNumMeshes << "meshes,"
546 << scene->mNumMaterials << "materials,"
547 << scene->mNumTextures << "embedded textures,"
548 << scene->mNumCameras << "cameras,"
549 << scene->mNumLights << "lights,"
550 << scene->mNumAnimations << "animations";
551 qDebug() << "Scene:";
552 printNodes(node: scene->mRootNode);
553 }
554
555 buildBuffer();
556 parseEmbeddedTextures();
557 parseMaterials();
558 parseCameras();
559 parseScene();
560 parseAnimations();
561
562 return true;
563}
564
565void AssimpImporter::printNodes(const aiNode *node, int level)
566{
567 qDebug().noquote() << QString().fill(c: '-', size: level * 4) << ai2qt(str: node->mName) << node->mNumMeshes << "mesh refs";
568 for (uint i = 0; i < node->mNumChildren; ++i)
569 printNodes(node: node->mChildren[i], level: level + 1);
570}
571
572template<class T> void copyIndexBuf(T *dst, const aiMesh *src)
573{
574 for (uint j = 0; j < src->mNumFaces; ++j) {
575 const aiFace *f = &src->mFaces[j];
576 if (f->mNumIndices != 3)
577 qFatal(msg: "Face %d is not a triangle (index count %d instead of 3)", j, f->mNumIndices);
578 *dst++ = f->mIndices[0];
579 *dst++ = f->mIndices[1];
580 *dst++ = f->mIndices[2];
581 }
582}
583
584static QString newBufferViewName()
585{
586 static int cnt = 0;
587 return QString(QStringLiteral("bufferView_%1")).arg(a: ++cnt);
588}
589
590static QString newAccessorName()
591{
592 static int cnt = 0;
593 return QString(QStringLiteral("accessor_%1")).arg(a: ++cnt);
594}
595
596static QString newMeshName()
597{
598 static int cnt = 0;
599 return QString(QStringLiteral("mesh_%1")).arg(a: ++cnt);
600}
601
602static QString newMaterialName()
603{
604 static int cnt = 0;
605 return QString(QStringLiteral("material_%1")).arg(a: ++cnt);
606}
607
608static QString newTechniqueName()
609{
610 static int cnt = 0;
611 return QString(QStringLiteral("technique_%1")).arg(a: ++cnt);
612}
613
614static QString newTextureName()
615{
616 static int cnt = 0;
617 return QString(QStringLiteral("texture_%1")).arg(a: ++cnt);
618}
619
620static QString newImageName()
621{
622 static int cnt = 0;
623 return QString(QStringLiteral("image_%1")).arg(a: ++cnt);
624}
625
626static QString newShaderName()
627{
628 static int cnt = 0;
629 return QString(QStringLiteral("shader_%1")).arg(a: ++cnt);
630}
631
632static QString newProgramName()
633{
634 static int cnt = 0;
635 return QString(QStringLiteral("program_%1")).arg(a: ++cnt);
636}
637
638static QString newNodeName()
639{
640 static int cnt = 0;
641 return QString(QStringLiteral("node_%1")).arg(a: ++cnt);
642}
643
644static QString newAnimationName()
645{
646 static int cnt = 0;
647 return QString(QStringLiteral("animation_%1")).arg(a: ++cnt);
648}
649
650template<class T> void calcBB(QVector<float> &minVal, QVector<float> &maxVal, T *data, int vertexCount, int compCount)
651{
652 minVal.resize(asize: compCount);
653 maxVal.resize(asize: compCount);
654 for (int i = 0; i < vertexCount; ++i) {
655 for (int j = 0; j < compCount; ++j) {
656 if (i == 0) {
657 minVal[j] = maxVal[j] = data[i][j];
658 } else {
659 if (data[i][j] < minVal[j])
660 minVal[j] = data[i][j];
661 if (data[i][j] > maxVal[j])
662 maxVal[j] = data[i][j];
663 }
664 }
665 }
666}
667
668// One buffer per importer (scene).
669// Two buffer views (array, index) + three or more accessors per mesh.
670
671void AssimpImporter::buildBuffer()
672{
673 m_buffer.clear();
674 m_meshInfo.clear();
675
676 if (opts.showLog)
677 qDebug() << "Meshes:";
678
679 const aiScene *sc = scene();
680 for (uint i = 0; i < sc->mNumMeshes; ++i) {
681 aiMesh *m = sc->mMeshes[i];
682 MeshInfo meshInfo;
683 meshInfo.originalName = ai2qt(str: m->mName);
684 meshInfo.name = newMeshName();
685 meshInfo.materialIndex = m->mMaterialIndex;
686
687 aiVector3D *vertices = m->mVertices;
688 aiVector3D *normals = m->mNormals;
689 aiVector3D *textureCoords = m->mTextureCoords[0];
690 aiColor4D *colors = m->mColors[0];
691 aiVector3D *tangents = m->mTangents;
692
693 if (opts.scale != 1) {
694 for (uint j = 0; j < m->mNumVertices; ++j) {
695 vertices[j].x *= opts.scale;
696 vertices[j].y *= opts.scale;
697 vertices[j].z *= opts.scale;
698 }
699 }
700
701 // Vertex (3), Normal (3), Coord? (2), Color? (4), Tangent? (3)
702 uint stride = 3 + 3 + (textureCoords ? 2 : 0) + (colors ? 4 : 0) + (tangents ? 3 : 0);
703 QByteArray vertexBuf;
704 vertexBuf.resize(size: stride * m->mNumVertices * sizeof(float));
705 float *p = reinterpret_cast<float *>(vertexBuf.data());
706
707 if (opts.interleave) {
708 for (uint j = 0; j < m->mNumVertices; ++j) {
709 // Vertex
710 *p++ = vertices[j].x;
711 *p++ = vertices[j].y;
712 *p++ = vertices[j].z;
713
714 // Normal
715 *p++ = normals[j].x;
716 *p++ = normals[j].y;
717 *p++ = normals[j].z;
718
719 // Coord
720 if (textureCoords) {
721 *p++ = textureCoords[j].x;
722 *p++ = textureCoords[j].y;
723 }
724
725 // Color
726 if (colors) {
727 *p++ = colors[j].r;
728 *p++ = colors[j].g;
729 *p++ = colors[j].b;
730 *p++ = colors[j].a;
731 }
732
733 // Tangent
734 if (tangents) {
735 *p++ = tangents[j].x;
736 *p++ = tangents[j].y;
737 *p++ = tangents[j].z;
738 }
739 }
740 } else {
741 // Vertex
742 for (uint j = 0; j < m->mNumVertices; ++j) {
743 *p++ = vertices[j].x;
744 *p++ = vertices[j].y;
745 *p++ = vertices[j].z;
746 }
747
748 // Normal
749 for (uint j = 0; j < m->mNumVertices; ++j) {
750 *p++ = normals[j].x;
751 *p++ = normals[j].y;
752 *p++ = normals[j].z;
753 }
754
755 // Coord
756 if (textureCoords) {
757 for (uint j = 0; j < m->mNumVertices; ++j) {
758 *p++ = textureCoords[j].x;
759 *p++ = textureCoords[j].y;
760 }
761 }
762
763 // Color
764 if (colors) {
765 for (uint j = 0; j < m->mNumVertices; ++j) {
766 *p++ = colors[j].r;
767 *p++ = colors[j].g;
768 *p++ = colors[j].b;
769 *p++ = colors[j].a;
770 }
771 }
772
773 // Tangent
774 if (tangents) {
775 for (uint j = 0; j < m->mNumVertices; ++j) {
776 *p++ = tangents[j].x;
777 *p++ = tangents[j].y;
778 *p++ = tangents[j].z;
779 }
780 }
781 }
782
783 MeshInfo::BufferView vertexBufView;
784 vertexBufView.name = newBufferViewName();
785 vertexBufView.length = vertexBuf.size();
786 vertexBufView.offset = m_buffer.size();
787 vertexBufView.componentType = GLT_FLOAT;
788 vertexBufView.target = GLT_ARRAY_BUFFER;
789 meshInfo.views.append(t: vertexBufView);
790
791 QByteArray indexBuf;
792 uint indexCount = m->mNumFaces * 3;
793 if (indexCount >= USHRT_MAX) {
794 indexBuf.resize(size: indexCount * sizeof(quint32));
795 quint32 *p = reinterpret_cast<quint32 *>(indexBuf.data());
796 copyIndexBuf(dst: p, src: m);
797 } else {
798 indexBuf.resize(size: indexCount * sizeof(quint16));
799 quint16 *p = reinterpret_cast<quint16 *>(indexBuf.data());
800 copyIndexBuf(dst: p, src: m);
801 }
802
803 MeshInfo::BufferView indexBufView;
804 indexBufView.name = newBufferViewName();
805 indexBufView.length = indexBuf.size();
806 indexBufView.offset = vertexBufView.offset + vertexBufView.length;
807 indexBufView.componentType = indexCount >= USHRT_MAX ? GLT_UNSIGNED_INT : GLT_UNSIGNED_SHORT;
808 indexBufView.target = GLT_ELEMENT_ARRAY_BUFFER;
809 meshInfo.views.append(t: indexBufView);
810
811 MeshInfo::Accessor acc;
812 uint startOffset = 0;
813 // Vertex
814 acc.name = newAccessorName();
815 acc.usage = QStringLiteral("POSITION");
816 acc.bufferView = vertexBufView.name;
817 acc.offset = 0;
818 acc.stride = opts.interleave ? stride * sizeof(float) : 3 * sizeof(float);
819 acc.count = m->mNumVertices;
820 acc.componentType = vertexBufView.componentType;
821 acc.type = QStringLiteral("VEC3");
822 calcBB(minVal&: acc.minVal, maxVal&: acc.maxVal, data: vertices, vertexCount: m->mNumVertices, compCount: 3);
823 meshInfo.accessors.append(t: acc);
824 startOffset += opts.interleave ? 3 : 3 * m->mNumVertices;
825 // Normal
826 acc.name = newAccessorName();
827 acc.usage = QStringLiteral("NORMAL");
828 acc.offset = startOffset * sizeof(float);
829 if (!opts.interleave)
830 acc.stride = 3 * sizeof(float);
831 calcBB(minVal&: acc.minVal, maxVal&: acc.maxVal, data: normals, vertexCount: m->mNumVertices, compCount: 3);
832 meshInfo.accessors.append(t: acc);
833 startOffset += opts.interleave ? 3 : 3 * m->mNumVertices;
834 // Coord
835 if (textureCoords) {
836 acc.name = newAccessorName();
837 acc.usage = QStringLiteral("TEXCOORD_0");
838 acc.offset = startOffset * sizeof(float);
839 if (!opts.interleave)
840 acc.stride = 2 * sizeof(float);
841 acc.type = QStringLiteral("VEC2");
842 calcBB(minVal&: acc.minVal, maxVal&: acc.maxVal, data: textureCoords, vertexCount: m->mNumVertices, compCount: 2);
843 meshInfo.accessors.append(t: acc);
844 startOffset += opts.interleave ? 2 : 2 * m->mNumVertices;
845 }
846 // Color
847 if (colors) {
848 acc.name = newAccessorName();
849 acc.usage = QStringLiteral("COLOR");
850 acc.offset = startOffset * sizeof(float);
851 if (!opts.interleave)
852 acc.stride = 4 * sizeof(float);
853 acc.type = QStringLiteral("VEC4");
854 calcBB(minVal&: acc.minVal, maxVal&: acc.maxVal, data: colors, vertexCount: m->mNumVertices, compCount: 4);
855 meshInfo.accessors.append(t: acc);
856 startOffset += opts.interleave ? 4 : 4 * m->mNumVertices;
857 }
858 // Tangent
859 if (tangents) {
860 acc.name = newAccessorName();
861 acc.usage = QStringLiteral("TANGENT");
862 acc.offset = startOffset * sizeof(float);
863 if (!opts.interleave)
864 acc.stride = 3 * sizeof(float);
865 acc.type = QStringLiteral("VEC3");
866 calcBB(minVal&: acc.minVal, maxVal&: acc.maxVal, data: tangents, vertexCount: m->mNumVertices, compCount: 3);
867 meshInfo.accessors.append(t: acc);
868 startOffset += opts.interleave ? 3 : 3 * m->mNumVertices;
869 }
870
871 // Index
872 acc.name = newAccessorName();
873 acc.usage = QStringLiteral("INDEX");
874 acc.bufferView = indexBufView.name;
875 acc.offset = 0;
876 acc.stride = 0;
877 acc.count = indexCount;
878 acc.componentType = indexBufView.componentType;
879 acc.type = QStringLiteral("SCALAR");
880 acc.minVal = acc.maxVal = QVector<float>();
881 meshInfo.accessors.append(t: acc);
882
883 if (opts.showLog) {
884 qDebug().noquote() << "#" << i << "(" << meshInfo.name << "/" << meshInfo.originalName << ")"
885 << m->mNumVertices << "vertices,"
886 << m->mNumFaces << "faces," << stride << "bytes per vertex,"
887 << vertexBuf.size() << "vertex bytes," << indexBuf.size() << "index bytes";
888 if (opts.scale != 1)
889 qDebug() << " scaled by" << opts.scale;
890 if (!opts.interleave)
891 qDebug() << " non-interleaved layout";
892 QStringList sl;
893 for (const MeshInfo::BufferView &bv : qAsConst(t&: meshInfo.views)) sl << bv.name;
894 qDebug() << " buffer views:" << sl;
895 sl.clear();
896 for (const MeshInfo::Accessor &acc : qAsConst(t&: meshInfo.accessors)) sl << acc.name;
897 qDebug() << " accessors:" << sl;
898 qDebug() << " material: #" << meshInfo.materialIndex;
899 }
900
901 m_buffer.append(a: vertexBuf);
902 m_buffer.append(a: indexBuf);
903
904 m_meshInfo.insert(akey: i, avalue: meshInfo);
905 }
906
907 if (opts.showLog)
908 qDebug().noquote() << "Total buffer size" << m_buffer.size();
909}
910
911void AssimpImporter::parseEmbeddedTextures()
912{
913#ifdef HAS_QIMAGE
914 m_embeddedTextures.clear();
915
916 const aiScene *sc = scene();
917 if (opts.showLog && sc->mNumTextures)
918 qDebug() << "Embedded textures:";
919
920 for (uint i = 0; i < sc->mNumTextures; ++i) {
921 aiTexture *t = sc->mTextures[i];
922 QImage img;
923 if (t->mHeight == 0) {
924 img = QImage::fromData(reinterpret_cast<uchar *>(t->pcData), t->mWidth);
925 } else {
926 uint sz = t->mWidth * t->mHeight;
927 QByteArray data;
928 data.resize(sz * 4);
929 uchar *p = reinterpret_cast<uchar *>(data.data());
930 for (uint j = 0; j < sz; ++j) {
931 *p++ = t->pcData[j].r;
932 *p++ = t->pcData[j].g;
933 *p++ = t->pcData[j].b;
934 *p++ = t->pcData[j].a;
935 }
936 img = QImage(reinterpret_cast<const uchar *>(data.constData()), t->mWidth, t->mHeight, QImage::Format_RGBA8888);
937 img.detach();
938 }
939 QString name;
940 static int imgCnt = 0;
941 name = QString(QStringLiteral("texture_%1.png")).arg(++imgCnt);
942 QString embeddedTextureRef = QStringLiteral("*") + QString::number(i); // see AI_MAKE_EMBEDDED_TEXNAME
943 m_embeddedTextures.insert(embeddedTextureRef, EmbeddedTextureInfo(name, img));
944 if (opts.showLog)
945 qDebug().noquote() << "#" << i << name << img;
946 }
947#else
948 if (scene()->mNumTextures)
949 qWarning() << "WARNING: No image support, ignoring" << scene()->mNumTextures << "embedded textures";
950#endif
951}
952
953void AssimpImporter::parseMaterials()
954{
955 m_materialInfo.clear();
956 m_externalTextures.clear();
957
958 if (opts.showLog)
959 qDebug() << "Materials:";
960
961 const aiScene *sc = scene();
962 for (uint i = 0; i < sc->mNumMaterials; ++i) {
963 const aiMaterial *mat = sc->mMaterials[i];
964 MaterialInfo matInfo;
965 matInfo.name = newMaterialName();
966
967 aiString s;
968 if (mat->Get(AI_MATKEY_NAME, pOut&: s) == aiReturn_SUCCESS)
969 matInfo.originalName = ai2qt(str: s);
970
971 aiColor4D color;
972 if (mat->Get(AI_MATKEY_COLOR_DIFFUSE, pOut&: color) == aiReturn_SUCCESS)
973 matInfo.m_colors.insert(akey: "diffuse", avalue: QVector<float>() << color.r << color.g << color.b << color.a);
974 if (mat->Get(AI_MATKEY_COLOR_SPECULAR, pOut&: color) == aiReturn_SUCCESS)
975 matInfo.m_colors.insert(akey: "specular", avalue: QVector<float>() << color.r << color.g << color.b);
976 if (mat->Get(AI_MATKEY_COLOR_AMBIENT, pOut&: color) == aiReturn_SUCCESS)
977 matInfo.m_colors.insert(akey: "ambient", avalue: QVector<float>() << color.r << color.g << color.b);
978
979 float f;
980 if (mat->Get(AI_MATKEY_SHININESS, pOut&: f) == aiReturn_SUCCESS)
981 matInfo.m_values.insert(akey: "shininess", avalue: f);
982
983 if (mat->GetTexture(type: aiTextureType_DIFFUSE, index: 0, path: &s) == aiReturn_SUCCESS)
984 matInfo.m_textures.insert(akey: "diffuse", avalue: ai2qt(str: s));
985 if (mat->GetTexture(type: aiTextureType_SPECULAR, index: 0, path: &s) == aiReturn_SUCCESS)
986 matInfo.m_textures.insert(akey: "specular", avalue: ai2qt(str: s));
987 if (mat->GetTexture(type: aiTextureType_NORMALS, index: 0, path: &s) == aiReturn_SUCCESS)
988 matInfo.m_textures.insert(akey: "normal", avalue: ai2qt(str: s));
989
990 QHash<QByteArray, QString>::iterator texIt = matInfo.m_textures.begin();
991 while (texIt != matInfo.m_textures.end()) {
992 // Map embedded texture references to real files.
993 if (texIt->startsWith(c: '*'))
994 *texIt = m_embeddedTextures[*texIt].name;
995 else
996 m_externalTextures.insert(value: *texIt);
997 ++texIt;
998 }
999
1000 m_materialInfo.insert(akey: i, avalue: matInfo);
1001
1002 if (opts.showLog) {
1003 qDebug().noquote() << "#" << i << "(" << matInfo.name << "/" << matInfo.originalName << ")";
1004 qDebug() << " colors:" << matInfo.m_colors;
1005 qDebug() << " values:" << matInfo.m_values;
1006 qDebug() << " textures:" << matInfo.m_textures;
1007 }
1008 }
1009}
1010
1011void AssimpImporter::parseCameras()
1012{
1013 m_cameraInfo.clear();
1014
1015 if (opts.showLog)
1016 qDebug() << "Cameras:";
1017
1018 const aiScene *sc = scene();
1019 for (uint i = 0; i < sc->mNumCameras; ++i) {
1020 const aiCamera *cam = sc->mCameras[i];
1021 QString name = ai2qt(str: cam->mName);
1022 CameraInfo c;
1023
1024 c.name = name + QStringLiteral("_cam");
1025 c.aspectRatio = qFuzzyIsNull(f: cam->mAspect) ? 1.5f : cam->mAspect;
1026 c.yfov = cam->mHorizontalFOV;
1027 if (c.yfov < (M_PI / 10.0)) // this can't be right (probably orthographic source camera)
1028 c.yfov = float(M_PI / 4.0);
1029 c.znear = cam->mClipPlaneNear;
1030 c.zfar = cam->mClipPlaneFar;
1031
1032 // Collada / glTF cameras point in -Z by default, the rest is in the
1033 // node matrix, no separate look-at params given here.
1034
1035 m_cameraInfo.insert(akey: name, avalue: c);
1036
1037 if (opts.showLog)
1038 qDebug().noquote() << "#" << i << "(" << name << ")" << c.aspectRatio << c.yfov << c.znear << c.zfar;
1039 }
1040}
1041
1042void AssimpImporter::parseNode(Importer::Node *dst, const aiNode *src)
1043{
1044 dst->name = ai2qt(str: src->mName);
1045 dst->uniqueName = newNodeName();
1046 for (uint j = 0; j < src->mNumChildren; ++j) {
1047 Node *c = new Node;
1048 parseNode(dst: c, src: src->mChildren[j]);
1049 dst->children << c;
1050 }
1051 dst->transformation = ai2qt(matrix: src->mTransformation);
1052 for (uint j = 0; j < src->mNumMeshes; ++j)
1053 dst->meshes << src->mMeshes[j];
1054}
1055
1056void AssimpImporter::parseScene()
1057{
1058 delNode(n: m_rootNode);
1059 const aiScene *sc = scene();
1060 m_rootNode = new Node;
1061 parseNode(dst: m_rootNode, src: sc->mRootNode);
1062}
1063
1064void AssimpImporter::addKeyFrame(QVector<KeyFrame> &keyFrames, float t, aiVector3D *vt, aiQuaternion *vr, aiVector3D *vs)
1065{
1066 KeyFrame kf;
1067 int idx = -1;
1068 for (int i = 0; i < keyFrames.count(); ++i) {
1069 if (qFuzzyCompare(p1: keyFrames[i].t, p2: t)) {
1070 kf = keyFrames[i];
1071 idx = i;
1072 break;
1073 }
1074 }
1075
1076 kf.t = t;
1077 if (vt) {
1078 kf.transValid = true;
1079 kf.trans = QVector<float>() << vt->x << vt->y << vt->z;
1080 }
1081 if (vr) {
1082 kf.rotValid = true;
1083 kf.rot = QVector<float>() << vr->w << vr->x << vr->y << vr->z;
1084 }
1085 if (vs) {
1086 kf.scaleValid = true;
1087 kf.scale = QVector<float>() << vs->x << vs->y << vs->z;
1088 }
1089
1090 if (idx >= 0)
1091 keyFrames[idx] = kf;
1092 else
1093 keyFrames.append(t: kf);
1094}
1095
1096void AssimpImporter::parseAnimations()
1097{
1098 const aiScene *sc = scene();
1099 if (opts.showLog && sc->mNumAnimations)
1100 qDebug() << "Animations:";
1101
1102 for (uint i = 0; i < sc->mNumAnimations; ++i) {
1103 const aiAnimation *anim = sc->mAnimations[i];
1104
1105 // Only care about node animations.
1106 for (uint j = 0; j < anim->mNumChannels; ++j) {
1107 const aiNodeAnim *a = anim->mChannels[j];
1108 AnimationInfo animInfo;
1109 QVector<KeyFrame> keyFrames;
1110
1111 if (opts.showLog)
1112 qDebug().noquote() << ai2qt(str: anim->mName) << "->" << ai2qt(str: a->mNodeName);
1113
1114 // Target values in the keyframes are local absolute (relative to parent, like node.matrix).
1115 for (uint kf = 0; kf < a->mNumPositionKeys; ++kf) {
1116 float t = float(a->mPositionKeys[kf].mTime);
1117 aiVector3D v = a->mPositionKeys[kf].mValue;
1118 animInfo.hasTranslation = true;
1119 addKeyFrame(keyFrames, t, vt: &v, vr: nullptr, vs: nullptr);
1120 }
1121 for (uint kf = 0; kf < a->mNumRotationKeys; ++kf) {
1122 float t = float(a->mRotationKeys[kf].mTime);
1123 aiQuaternion v = a->mRotationKeys[kf].mValue;
1124 animInfo.hasRotation = true;
1125 addKeyFrame(keyFrames, t, vt: nullptr, vr: &v, vs: nullptr);
1126 }
1127 for (uint kf = 0; kf < a->mNumScalingKeys; ++kf) {
1128 float t = float(a->mScalingKeys[kf].mTime);
1129 aiVector3D v = a->mScalingKeys[kf].mValue;
1130 animInfo.hasScale = true;
1131 addKeyFrame(keyFrames, t, vt: nullptr, vr: nullptr, vs: &v);
1132 }
1133
1134 // Here we should ideally get rid of non-animated properties (that
1135 // just set the t-r-s value from node.matrix in every frame) but
1136 // let's leave that as a future exercise.
1137
1138 if (!keyFrames.isEmpty()) {
1139 animInfo.name = ai2qt(str: anim->mName);
1140 QString nodeName = ai2qt(str: a->mNodeName); // have to map to our generated, unique node names
1141 const Node *targetNode = findNode(root: m_rootNode, originalName: nodeName);
1142 if (targetNode)
1143 animInfo.targetNode = targetNode->uniqueName;
1144 else
1145 qWarning().noquote() << "ERROR: Cannot find target node" << nodeName << "for animation" << animInfo.name;
1146 animInfo.keyFrames = keyFrames;
1147 m_animations << animInfo;
1148
1149 if (opts.showLog) {
1150 for (const KeyFrame &kf : qAsConst(t&: keyFrames)) {
1151 QString msg;
1152 QTextStream s(&msg);
1153 s << " @ " << kf.t;
1154 if (kf.transValid)
1155 s << " T=(" << kf.trans[0] << ", " << kf.trans[1] << ", " << kf.trans[2] << ")";
1156 if (kf.rotValid)
1157 s << " R=(w=" << kf.rot[0] << ", " << kf.rot[1] << ", " << kf.rot[2] << ", " << kf.rot[3] << ")";
1158 if (kf.scaleValid)
1159 s << " S=(" << kf.scale[0] << ", " << kf.scale[1] << ", " << kf.scale[2] << ")";
1160 qDebug().noquote() << msg;
1161 }
1162 }
1163 }
1164 }
1165 }
1166}
1167
1168const aiScene *AssimpImporter::scene() const
1169{
1170 return m_importer->GetScene();
1171}
1172
1173class Exporter
1174{
1175public:
1176 Exporter(Importer *importer) : m_importer(importer) { }
1177 virtual ~Exporter() { }
1178
1179 virtual void save(const QString &inputFilename) = 0;
1180
1181protected:
1182 bool nodeIsUseful(const Importer::Node *n) const;
1183 void copyExternalTextures(const QString &inputFilename);
1184 void exportEmbeddedTextures();
1185 void compressTextures();
1186
1187 Importer *m_importer;
1188 QSet<QString> m_files;
1189 QHash<QString, QString> m_compressedTextures;
1190};
1191
1192bool Exporter::nodeIsUseful(const Importer::Node *n) const
1193{
1194 if (!n->meshes.isEmpty() || m_importer->cameraInfo().contains(akey: n->name))
1195 return true;
1196
1197 for (const Importer::Node *c : n->children) {
1198 if (nodeIsUseful(n: c))
1199 return true;
1200 }
1201
1202 return false;
1203}
1204
1205void Exporter::copyExternalTextures(const QString &inputFilename)
1206{
1207 const auto textureFilenames = m_importer->externalTextures();
1208 for (const QString &textureFilename : textureFilenames) {
1209 const QString dst = opts.outDir + textureFilename;
1210 m_files.insert(value: QFileInfo(dst).fileName());
1211 // External textures need copying only when output dir was specified.
1212 if (!opts.outDir.isEmpty()) {
1213 const QString src = QFileInfo(inputFilename).path() + QStringLiteral("/") + textureFilename;
1214 if (QFileInfo(src).absolutePath() != QFileInfo(dst).absolutePath()) {
1215 if (opts.showLog)
1216 qDebug().noquote() << "Copying" << src << "to" << dst;
1217 QFile(src).copy(newName: dst);
1218 }
1219 }
1220 }
1221}
1222
1223void Exporter::exportEmbeddedTextures()
1224{
1225#ifdef HAS_QIMAGE
1226 const auto embeddedTextures = m_importer->embeddedTextures();
1227 for (const Importer::EmbeddedTextureInfo &embTex : embeddedTextures) {
1228 QString fn = opts.outDir + embTex.name;
1229 m_files.insert(QFileInfo(fn).fileName());
1230 if (opts.showLog)
1231 qDebug().noquote() << "Writing" << fn;
1232 embTex.image.save(fn);
1233 }
1234#endif
1235}
1236
1237void Exporter::compressTextures()
1238{
1239 if (opts.texComp != Options::ETC1)
1240 return;
1241
1242 const auto textureFilenames = m_importer->externalTextures();
1243 const auto embeddedTextures = m_importer->embeddedTextures();
1244 QStringList imageList;
1245 imageList.reserve(alloc: textureFilenames.size() + embeddedTextures.size());
1246 for (const QString &textureFilename : textureFilenames)
1247 imageList << opts.outDir + textureFilename;
1248 for (const Importer::EmbeddedTextureInfo &embTex : embeddedTextures)
1249 imageList << opts.outDir + embTex.name;
1250
1251 for (const QString &filename : qAsConst(t&: imageList)) {
1252 if (QFileInfo(filename).suffix().toLower() != QStringLiteral("png"))
1253 continue;
1254 QByteArray cmd = QByteArrayLiteral("etc1tool ");
1255 cmd += filename.toUtf8();
1256 qDebug().noquote() << "Invoking" << cmd;
1257 // No QProcess in bootstrap
1258 if (system(command: cmd.constData()) == -1) {
1259 qWarning() << "ERROR: Failed to launch etc1tool";
1260 } else {
1261 QString src = QFileInfo(filename).fileName();
1262 QString dst = QFileInfo(src).baseName() + QStringLiteral(".pkm");
1263 m_compressedTextures.insert(akey: src, avalue: dst);
1264 m_files.remove(value: src);
1265 m_files.insert(value: dst);
1266 }
1267 }
1268}
1269
1270class GltfExporter : public Exporter
1271{
1272public:
1273 GltfExporter(Importer *importer);
1274 void save(const QString &inputFilename) override;
1275
1276private:
1277 struct ProgramInfo {
1278 struct Param {
1279 Param() : type(0) { }
1280 Param(QString name, QString nameInShader, QString semantic, uint type)
1281 : name(name), nameInShader(nameInShader), semantic(semantic), type(type) { }
1282 QString name;
1283 QString nameInShader;
1284 QString semantic;
1285 uint type;
1286 };
1287 QString commonTechniqueName;
1288 QString vertShader;
1289 QString fragShader;
1290 QVector<Param> attributes;
1291 QVector<Param> uniforms;
1292 };
1293 friend class QTypeInfo<ProgramInfo>;
1294 friend class QTypeInfo<ProgramInfo::Param>;
1295
1296 void writeShader(const QString &src, const QString &dst, const QVector<QPair<QByteArray, QByteArray> > &substTab);
1297 QString exportNode(const Importer::Node *n, QJsonObject &nodes);
1298 void exportMaterials(QJsonObject &materials, QHash<QString, QString> *textureNameMap);
1299 void exportParameter(QJsonObject &dst, const QVector<ProgramInfo::Param> &params);
1300 void exportTechniques(QJsonObject &obj, const QString &basename);
1301 void exportAnimations(QJsonObject &obj, QVector<Importer::BufferInfo> &bufList,
1302 QVector<Importer::MeshInfo::BufferView> &bvList,
1303 QVector<Importer::MeshInfo::Accessor> &accList);
1304 void initShaderInfo();
1305 ProgramInfo *chooseProgram(uint materialIndex);
1306
1307 QJsonObject m_obj;
1308 QJsonDocument m_doc;
1309 QVector<ProgramInfo> m_progs;
1310
1311 struct TechniqueInfo {
1312 TechniqueInfo() : opaque(true), prog(nullptr) { }
1313 TechniqueInfo(const QString &name, bool opaque, ProgramInfo *prog)
1314 : name(name)
1315 , opaque(opaque)
1316 , prog(prog)
1317 {
1318 coreName = name + QStringLiteral("_core");
1319 gl2Name = name + QStringLiteral("_gl2");
1320 }
1321 QString name;
1322 QString coreName;
1323 QString gl2Name;
1324 bool opaque;
1325 ProgramInfo *prog;
1326 };
1327 friend class QTypeInfo<TechniqueInfo>;
1328 QVector<TechniqueInfo> m_techniques;
1329 QSet<ProgramInfo *> m_usedPrograms;
1330
1331 QVector<QPair<QByteArray, QByteArray> > m_subst_es2;
1332 QVector<QPair<QByteArray, QByteArray> > m_subst_core;
1333
1334 QHash<QString, bool> m_imageHasAlpha;
1335};
1336QT_BEGIN_NAMESPACE
1337Q_DECLARE_TYPEINFO(GltfExporter::ProgramInfo, Q_MOVABLE_TYPE);
1338Q_DECLARE_TYPEINFO(GltfExporter::ProgramInfo::Param, Q_MOVABLE_TYPE);
1339Q_DECLARE_TYPEINFO(GltfExporter::TechniqueInfo, Q_MOVABLE_TYPE);
1340QT_END_NAMESPACE
1341
1342GltfExporter::GltfExporter(Importer *importer)
1343 : Exporter(importer)
1344{
1345 initShaderInfo();
1346}
1347
1348struct Shader {
1349 const char *name;
1350 const char *text;
1351} shaders[] = {
1352 {
1353 .name: "color.vert",
1354.text: "$VERSION\n"
1355"$ATTRIBUTE vec3 vertexPosition;\n"
1356"$ATTRIBUTE vec3 vertexNormal;\n"
1357"$VVARYING vec3 vPosition;\n"
1358"$VVARYING vec3 vNormal;\n"
1359"uniform mat4 projection;\n"
1360"uniform mat4 modelView;\n"
1361"uniform mat3 modelViewNormal;\n"
1362"void main()\n"
1363"{\n"
1364" vNormal = normalize( modelViewNormal * vertexNormal );\n"
1365" vPosition = vec3( modelView * vec4( vertexPosition, 1.0 ) );\n"
1366" gl_Position = projection * modelView * vec4( vertexPosition, 1.0 );\n"
1367"}\n"
1368 },
1369 {
1370 .name: "color.frag",
1371.text: "$VERSION\n"
1372"uniform $HIGHP vec4 lightPosition;\n"
1373"uniform $HIGHP vec3 lightIntensity;\n"
1374"uniform $HIGHP vec3 ka;\n"
1375"uniform $HIGHP vec4 kd;\n"
1376"uniform $HIGHP vec3 ks;\n"
1377"uniform $HIGHP float shininess;\n"
1378"$FVARYING $HIGHP vec3 vPosition;\n"
1379"$FVARYING $HIGHP vec3 vNormal;\n"
1380"$DECL_FRAGCOLOR\n"
1381"$HIGHP vec3 adsModel( const $HIGHP vec3 pos, const $HIGHP vec3 n )\n"
1382"{\n"
1383" $HIGHP vec3 s = normalize( vec3( lightPosition ) - pos );\n"
1384" $HIGHP vec3 v = normalize( -pos );\n"
1385" $HIGHP vec3 r = reflect( -s, n );\n"
1386" $HIGHP float diffuse = max( dot( s, n ), 0.0 );\n"
1387" $HIGHP float specular = 0.0;\n"
1388" if ( dot( s, n ) > 0.0 )\n"
1389" specular = pow( max( dot( r, v ), 0.0 ), shininess );\n"
1390" return lightIntensity * ( ka + kd.rgb * diffuse + ks * specular );\n"
1391"}\n"
1392"void main()\n"
1393"{\n"
1394" $FRAGCOLOR = vec4( adsModel( vPosition, normalize( vNormal ) ) * kd.a, kd.a );\n"
1395"}\n"
1396 },
1397 {
1398 .name: "diffusemap.vert",
1399.text: "$VERSION\n"
1400"$ATTRIBUTE vec3 vertexPosition;\n"
1401"$ATTRIBUTE vec3 vertexNormal;\n"
1402"$ATTRIBUTE vec2 vertexTexCoord;\n"
1403"$VVARYING vec3 vPosition;\n"
1404"$VVARYING vec3 vNormal;\n"
1405"$VVARYING vec2 vTexCoord;\n"
1406"uniform mat4 projection;\n"
1407"uniform mat4 modelView;\n"
1408"uniform mat3 modelViewNormal;\n"
1409"void main()\n"
1410"{\n"
1411" vTexCoord = vertexTexCoord;\n"
1412" vNormal = normalize( modelViewNormal * vertexNormal );\n"
1413" vPosition = vec3( modelView * vec4( vertexPosition, 1.0 ) );\n"
1414" gl_Position = projection * modelView * vec4( vertexPosition, 1.0 );\n"
1415"}\n"
1416 },
1417 {
1418 .name: "diffusemap.frag",
1419.text: "$VERSION\n"
1420"uniform $HIGHP vec4 lightPosition;\n"
1421"uniform $HIGHP vec3 lightIntensity;\n"
1422"uniform $HIGHP vec3 ka;\n"
1423"uniform $HIGHP vec3 ks;\n"
1424"uniform $HIGHP float shininess;\n"
1425"uniform sampler2D diffuseTexture;\n"
1426"$FVARYING $HIGHP vec3 vPosition;\n"
1427"$FVARYING $HIGHP vec3 vNormal;\n"
1428"$FVARYING $HIGHP vec2 vTexCoord;\n"
1429"$DECL_FRAGCOLOR\n"
1430"$HIGHP vec4 adsModel( const $HIGHP vec3 pos, const $HIGHP vec3 n )\n"
1431"{\n"
1432" $HIGHP vec3 s = normalize( vec3( lightPosition ) - pos );\n"
1433" $HIGHP vec3 v = normalize( -pos );\n"
1434" $HIGHP vec3 r = reflect( -s, n );\n"
1435" $HIGHP float diffuse = max( dot( s, n ), 0.0 );\n"
1436" $HIGHP float specular = 0.0;\n"
1437" if ( dot( s, n ) > 0.0 )\n"
1438" specular = pow( max( dot( r, v ), 0.0 ), shininess );\n"
1439" $HIGHP vec4 kd = $TEXTURE2D( diffuseTexture, vTexCoord );\n"
1440" return vec4( lightIntensity * ( ka + kd.rgb * diffuse + ks * specular ) * kd.a, kd.a );\n"
1441"}\n"
1442"void main()\n"
1443"{\n"
1444" $FRAGCOLOR = adsModel( vPosition, normalize( vNormal ) );\n"
1445"}\n"
1446 },
1447 {
1448 .name: "diffusespecularmap.frag",
1449.text: "$VERSION\n"
1450"uniform $HIGHP vec4 lightPosition;\n"
1451"uniform $HIGHP vec3 lightIntensity;\n"
1452"uniform $HIGHP vec3 ka;\n"
1453"uniform $HIGHP float shininess;\n"
1454"uniform sampler2D diffuseTexture;\n"
1455"uniform sampler2D specularTexture;\n"
1456"$FVARYING $HIGHP vec3 vPosition;\n"
1457"$FVARYING $HIGHP vec3 vNormal;\n"
1458"$FVARYING $HIGHP vec2 vTexCoord;\n"
1459"$DECL_FRAGCOLOR\n"
1460"$HIGHP vec4 adsModel( const in $HIGHP vec3 pos, const in $HIGHP vec3 n )\n"
1461"{\n"
1462" $HIGHP vec3 s = normalize( vec3( lightPosition ) - pos );\n"
1463" $HIGHP vec3 v = normalize( -pos );\n"
1464" $HIGHP vec3 r = reflect( -s, n );\n"
1465" $HIGHP float diffuse = max( dot( s, n ), 0.0 );\n"
1466" $HIGHP float specular = 0.0;\n"
1467" if ( dot( s, n ) > 0.0 )\n"
1468" specular = ( shininess / ( 8.0 * 3.14 ) ) * pow( max( dot( r, v ), 0.0 ), shininess );\n"
1469" $HIGHP vec4 kd = $TEXTURE2D( diffuseTexture, vTexCoord );\n"
1470" $HIGHP vec3 ks = $TEXTURE2D( specularTexture, vTexCoord );\n"
1471" return vec4( lightIntensity * ( ka + kd.rgb * diffuse + ks * specular ) * kd.a, kd.a );\n"
1472"}\n"
1473"void main()\n"
1474"{\n"
1475" $FRAGCOLOR = vec4( adsModel( vPosition, normalize( vNormal ) ), 1.0 );\n"
1476"}\n"
1477 },
1478 {
1479 .name: "normaldiffusemap.vert",
1480.text: "$VERSION\n"
1481"$ATTRIBUTE vec3 vertexPosition;\n"
1482"$ATTRIBUTE vec3 vertexNormal;\n"
1483"$ATTRIBUTE vec2 vertexTexCoord;\n"
1484"$ATTRIBUTE vec4 vertexTangent;\n"
1485"$VVARYING vec3 lightDir;\n"
1486"$VVARYING vec3 viewDir;\n"
1487"$VVARYING vec2 texCoord;\n"
1488"uniform mat4 projection;\n"
1489"uniform mat4 modelView;\n"
1490"uniform mat3 modelViewNormal;\n"
1491"uniform vec4 lightPosition;\n"
1492"void main()\n"
1493"{\n"
1494" texCoord = vertexTexCoord;\n"
1495" vec3 normal = normalize( modelViewNormal * vertexNormal );\n"
1496" vec3 tangent = normalize( modelViewNormal * vertexTangent.xyz );\n"
1497" vec3 position = vec3( modelView * vec4( vertexPosition, 1.0 ) );\n"
1498" vec3 binormal = normalize( cross( normal, tangent ) );\n"
1499" mat3 tangentMatrix = mat3 (\n"
1500" tangent.x, binormal.x, normal.x,\n"
1501" tangent.y, binormal.y, normal.y,\n"
1502" tangent.z, binormal.z, normal.z );\n"
1503" vec3 s = vec3( lightPosition ) - position;\n"
1504" lightDir = normalize( tangentMatrix * s );\n"
1505" vec3 v = -position;\n"
1506" viewDir = normalize( tangentMatrix * v );\n"
1507" gl_Position = projection * modelView * vec4( vertexPosition, 1.0 );\n"
1508"}\n"
1509 },
1510 {
1511 .name: "normaldiffusemap.frag",
1512.text: "$VERSION\n"
1513"uniform $HIGHP vec3 lightIntensity;\n"
1514"uniform $HIGHP vec3 ka;\n"
1515"uniform $HIGHP vec3 ks;\n"
1516"uniform $HIGHP float shininess;\n"
1517"uniform sampler2D diffuseTexture;\n"
1518"uniform sampler2D normalTexture;\n"
1519"$FVARYING $HIGHP vec3 lightDir;\n"
1520"$FVARYING $HIGHP vec3 viewDir;\n"
1521"$FVARYING $HIGHP vec2 texCoord;\n"
1522"$DECL_FRAGCOLOR\n"
1523"$HIGHP vec3 adsModel( const $HIGHP vec3 norm, const $HIGHP vec3 diffuseReflect)\n"
1524"{\n"
1525" $HIGHP vec3 r = reflect( -lightDir, norm );\n"
1526" $HIGHP vec3 ambient = lightIntensity * ka;\n"
1527" $HIGHP float sDotN = max( dot( lightDir, norm ), 0.0 );\n"
1528" $HIGHP vec3 diffuse = lightIntensity * diffuseReflect * sDotN;\n"
1529" $HIGHP vec3 ambientAndDiff = ambient + diffuse;\n"
1530" $HIGHP vec3 spec = vec3( 0.0 );\n"
1531" if ( sDotN > 0.0 )\n"
1532" spec = lightIntensity * ks * pow( max( dot( r, viewDir ), 0.0 ), shininess );\n"
1533" return ambientAndDiff + spec;\n"
1534"}\n"
1535"void main()\n"
1536"{\n"
1537" $HIGHP vec4 kd = $TEXTURE2D( diffuseTexture, texCoord );\n"
1538" $HIGHP vec4 normal = 2.0 * $TEXTURE2D( normalTexture, texCoord ) - vec4( 1.0 );\n"
1539" $FRAGCOLOR = vec4( adsModel( normalize( normal.xyz ), kd.rgb) * kd.a, kd.a );\n"
1540"}\n"
1541 },
1542 {
1543 .name: "normaldiffusespecularmap.frag",
1544.text: "$VERSION\n"
1545"uniform $HIGHP vec3 lightIntensity;\n"
1546"uniform $HIGHP vec3 ka;\n"
1547"uniform $HIGHP float shininess;\n"
1548"uniform sampler2D diffuseTexture;\n"
1549"uniform sampler2D specularTexture;\n"
1550"uniform sampler2D normalTexture;\n"
1551"$FVARYING $HIGHP vec3 lightDir;\n"
1552"$FVARYING $HIGHP vec3 viewDir;\n"
1553"$FVARYING $HIGHP vec2 texCoord;\n"
1554"$DECL_FRAGCOLOR\n"
1555"$HIGHP vec3 adsModel( const $HIGHP vec3 norm, const $HIGHP vec3 diffuseReflect, const $HIGHP vec3 specular )\n"
1556"{\n"
1557" $HIGHP vec3 r = reflect( -lightDir, norm );\n"
1558" $HIGHP vec3 ambient = lightIntensity * ka;\n"
1559" $HIGHP float sDotN = max( dot( lightDir, norm ), 0.0 );\n"
1560" $HIGHP vec3 diffuse = lightIntensity * diffuseReflect * sDotN;\n"
1561" $HIGHP vec3 ambientAndDiff = ambient + diffuse;\n"
1562" $HIGHP vec3 spec = vec3( 0.0 );\n"
1563" if ( sDotN > 0.0 )\n"
1564" spec = lightIntensity * ( shininess / ( 8.0 * 3.14 ) ) * pow( max( dot( r, viewDir ), 0.0 ), shininess );\n"
1565" return (ambientAndDiff + spec * specular.rgb);\n"
1566"}\n"
1567"void main()\n"
1568"{\n"
1569" $HIGHP vec4 kd = $TEXTURE2D( diffuseTexture, texCoord );\n"
1570" $HIGHP vec3 ks = $TEXTURE2D( specularTexture, texCoord );\n"
1571" $HIGHP vec4 normal = 2.0 * $TEXTURE2D( normalTexture, texCoord ) - vec4( 1.0 );\n"
1572" $FRAGCOLOR = vec4( adsModel( normalize( normal.xyz ), kd.rgb, ks ) * kd.a, kd.a );\n"
1573"}\n"
1574 }
1575};
1576
1577void GltfExporter::initShaderInfo()
1578{
1579 ProgramInfo p;
1580
1581 p = ProgramInfo();
1582 p.commonTechniqueName = "PHONG"; // diffuse RGBA, specular RGBA
1583 p.vertShader = "color.vert";
1584 p.fragShader = "color.frag";
1585 p.attributes << ProgramInfo::Param("position", "vertexPosition", "POSITION", GLT_FLOAT_VEC3);
1586 p.attributes << ProgramInfo::Param("normal", "vertexNormal", "NORMAL", GLT_FLOAT_VEC3);
1587 p.uniforms << ProgramInfo::Param("projection", "projection", "PROJECTION", GLT_FLOAT_MAT4);
1588 p.uniforms << ProgramInfo::Param("modelView", "modelView", "MODELVIEW", GLT_FLOAT_MAT4);
1589 p.uniforms << ProgramInfo::Param("normalMatrix", "modelViewNormal", "MODELVIEWINVERSETRANSPOSE", GLT_FLOAT_MAT3);
1590 p.uniforms << ProgramInfo::Param("lightPosition", "lightPosition", QString(), GLT_FLOAT_VEC4);
1591 p.uniforms << ProgramInfo::Param("lightIntensity", "lightIntensity", QString(), GLT_FLOAT_VEC3);
1592 p.uniforms << ProgramInfo::Param("ambient", "ka", QString(), GLT_FLOAT_VEC3);
1593 p.uniforms << ProgramInfo::Param("diffuse", "kd", QString(), GLT_FLOAT_VEC4);
1594 p.uniforms << ProgramInfo::Param("specular", "ks", QString(), GLT_FLOAT_VEC3);
1595 p.uniforms << ProgramInfo::Param("shininess", "shininess", QString(), GLT_FLOAT);
1596 m_progs << p;
1597
1598 p = ProgramInfo();
1599 p.commonTechniqueName = "PHONG"; // diffuse texture, specular RGBA
1600 p.vertShader = "diffusemap.vert";
1601 p.fragShader = "diffusemap.frag";
1602 p.attributes << ProgramInfo::Param("position", "vertexPosition", "POSITION", GLT_FLOAT_VEC3);
1603 p.attributes << ProgramInfo::Param("normal", "vertexNormal", "NORMAL", GLT_FLOAT_VEC3);
1604 p.attributes << ProgramInfo::Param("texcoord0", "vertexTexCoord", "TEXCOORD_0", GLT_FLOAT_VEC2);
1605 p.uniforms << ProgramInfo::Param("projection", "projection", "PROJECTION", GLT_FLOAT_MAT4);
1606 p.uniforms << ProgramInfo::Param("modelView", "modelView", "MODELVIEW", GLT_FLOAT_MAT4);
1607 p.uniforms << ProgramInfo::Param("normalMatrix", "modelViewNormal", "MODELVIEWINVERSETRANSPOSE", GLT_FLOAT_MAT3);
1608 p.uniforms << ProgramInfo::Param("lightPosition", "lightPosition", QString(), GLT_FLOAT_VEC4);
1609 p.uniforms << ProgramInfo::Param("lightIntensity", "lightIntensity", QString(), GLT_FLOAT_VEC3);
1610 p.uniforms << ProgramInfo::Param("ambient", "ka", QString(), GLT_FLOAT_VEC3);
1611 p.uniforms << ProgramInfo::Param("specular", "ks", QString(), GLT_FLOAT_VEC3);
1612 p.uniforms << ProgramInfo::Param("shininess", "shininess", QString(), GLT_FLOAT);
1613 p.uniforms << ProgramInfo::Param("diffuse", "diffuseTexture", QString(), GLT_SAMPLER_2D);
1614 m_progs << p;
1615
1616 p = ProgramInfo();
1617 p.commonTechniqueName = "PHONG"; // diffuse texture, specular texture
1618 p.vertShader = "diffusemap.vert";
1619 p.fragShader = "diffusespecularmap.frag";
1620 p.attributes << ProgramInfo::Param("position", "vertexPosition", "POSITION", GLT_FLOAT_VEC3);
1621 p.attributes << ProgramInfo::Param("normal", "vertexNormal", "NORMAL", GLT_FLOAT_VEC3);
1622 p.attributes << ProgramInfo::Param("texcoord0", "vertexTexCoord", "TEXCOORD_0", GLT_FLOAT_VEC2);
1623 p.uniforms << ProgramInfo::Param("projection", "projection", "PROJECTION", GLT_FLOAT_MAT4);
1624 p.uniforms << ProgramInfo::Param("modelView", "modelView", "MODELVIEW", GLT_FLOAT_MAT4);
1625 p.uniforms << ProgramInfo::Param("normalMatrix", "modelViewNormal", "MODELVIEWINVERSETRANSPOSE", GLT_FLOAT_MAT3);
1626 p.uniforms << ProgramInfo::Param("lightPosition", "lightPosition", QString(), GLT_FLOAT_VEC4);
1627 p.uniforms << ProgramInfo::Param("lightIntensity", "lightIntensity", QString(), GLT_FLOAT_VEC3);
1628 p.uniforms << ProgramInfo::Param("ambient", "ka", QString(), GLT_FLOAT_VEC3);
1629 p.uniforms << ProgramInfo::Param("shininess", "shininess", QString(), GLT_FLOAT);
1630 p.uniforms << ProgramInfo::Param("diffuse", "diffuseTexture", QString(), GLT_SAMPLER_2D);
1631 p.uniforms << ProgramInfo::Param("specular", "specularTexture", QString(), GLT_SAMPLER_2D);
1632 m_progs << p;
1633
1634 p = ProgramInfo();
1635 p.commonTechniqueName = "PHONG"; // diffuse texture, specular RGBA, normalmap texture
1636 p.vertShader = "normaldiffusemap.vert";
1637 p.fragShader = "normaldiffusemap.frag";
1638 p.attributes << ProgramInfo::Param("position", "vertexPosition", "POSITION", GLT_FLOAT_VEC3);
1639 p.attributes << ProgramInfo::Param("normal", "vertexNormal", "NORMAL", GLT_FLOAT_VEC3);
1640 p.attributes << ProgramInfo::Param("texcoord0", "vertexTexCoord", "TEXCOORD_0", GLT_FLOAT_VEC2);
1641 p.attributes << ProgramInfo::Param("tangent", "vertexTangent", "TANGENT", GLT_FLOAT_VEC3);
1642 p.uniforms << ProgramInfo::Param("projection", "projection", "PROJECTION", GLT_FLOAT_MAT4);
1643 p.uniforms << ProgramInfo::Param("modelView", "modelView", "MODELVIEW", GLT_FLOAT_MAT4);
1644 p.uniforms << ProgramInfo::Param("normalMatrix", "modelViewNormal", "MODELVIEWINVERSETRANSPOSE", GLT_FLOAT_MAT3);
1645 p.uniforms << ProgramInfo::Param("lightPosition", "lightPosition", QString(), GLT_FLOAT_VEC4);
1646 p.uniforms << ProgramInfo::Param("lightIntensity", "lightIntensity", QString(), GLT_FLOAT_VEC3);
1647 p.uniforms << ProgramInfo::Param("ambient", "ka", QString(), GLT_FLOAT_VEC3);
1648 p.uniforms << ProgramInfo::Param("specular", "ks", QString(), GLT_FLOAT_VEC3);
1649 p.uniforms << ProgramInfo::Param("shininess", "shininess", QString(), GLT_FLOAT);
1650 p.uniforms << ProgramInfo::Param("diffuse", "diffuseTexture", QString(), GLT_SAMPLER_2D);
1651 p.uniforms << ProgramInfo::Param("normalmap", "normalTexture", QString(), GLT_SAMPLER_2D);
1652 m_progs << p;
1653
1654 p = ProgramInfo();
1655 p.commonTechniqueName = "PHONG"; // diffuse texture, specular texture, normalmap texture
1656 p.vertShader = "normaldiffusemap.vert";
1657 p.fragShader = "normaldiffusespecularmap.frag";
1658 p.attributes << ProgramInfo::Param("position", "vertexPosition", "POSITION", GLT_FLOAT_VEC3);
1659 p.attributes << ProgramInfo::Param("normal", "vertexNormal", "NORMAL", GLT_FLOAT_VEC3);
1660 p.attributes << ProgramInfo::Param("texcoord0", "vertexTexCoord", "TEXCOORD_0", GLT_FLOAT_VEC2);
1661 p.attributes << ProgramInfo::Param("tangent", "vertexTangent", "TANGENT", GLT_FLOAT_VEC3);
1662 p.uniforms << ProgramInfo::Param("projection", "projection", "PROJECTION", GLT_FLOAT_MAT4);
1663 p.uniforms << ProgramInfo::Param("modelView", "modelView", "MODELVIEW", GLT_FLOAT_MAT4);
1664 p.uniforms << ProgramInfo::Param("normalMatrix", "modelViewNormal", "MODELVIEWINVERSETRANSPOSE", GLT_FLOAT_MAT3);
1665 p.uniforms << ProgramInfo::Param("lightPosition", "lightPosition", QString(), GLT_FLOAT_VEC4);
1666 p.uniforms << ProgramInfo::Param("lightIntensity", "lightIntensity", QString(), GLT_FLOAT_VEC3);
1667 p.uniforms << ProgramInfo::Param("ambient", "ka", QString(), GLT_FLOAT_VEC3);
1668 p.uniforms << ProgramInfo::Param("shininess", "shininess", QString(), GLT_FLOAT);
1669 p.uniforms << ProgramInfo::Param("diffuse", "diffuseTexture", QString(), GLT_SAMPLER_2D);
1670 p.uniforms << ProgramInfo::Param("specular", "specularTexture", QString(), GLT_SAMPLER_2D);
1671 p.uniforms << ProgramInfo::Param("normalmap", "normalTexture", QString(), GLT_SAMPLER_2D);
1672 m_progs << p;
1673
1674 m_subst_es2 << qMakePair(QByteArrayLiteral("$VERSION"), y: QByteArray());
1675 m_subst_es2 << qMakePair(QByteArrayLiteral("$ATTRIBUTE"), QByteArrayLiteral("attribute"));
1676 m_subst_es2 << qMakePair(QByteArrayLiteral("$VVARYING"), QByteArrayLiteral("varying"));
1677 m_subst_es2 << qMakePair(QByteArrayLiteral("$FVARYING"), QByteArrayLiteral("varying"));
1678 m_subst_es2 << qMakePair(QByteArrayLiteral("$TEXTURE2D"), QByteArrayLiteral("texture2D"));
1679 m_subst_es2 << qMakePair(QByteArrayLiteral("$DECL_FRAGCOLOR"), y: QByteArray());
1680 m_subst_es2 << qMakePair(QByteArrayLiteral("$FRAGCOLOR"), QByteArrayLiteral("gl_FragColor"));
1681 m_subst_es2 << qMakePair(QByteArrayLiteral("$HIGHP"), QByteArrayLiteral("highp"));
1682
1683 m_subst_core << qMakePair(QByteArrayLiteral("$VERSION"), QByteArrayLiteral("#version 150 core"));
1684 m_subst_core << qMakePair(QByteArrayLiteral("$ATTRIBUTE"), QByteArrayLiteral("in"));
1685 m_subst_core << qMakePair(QByteArrayLiteral("$VVARYING"), QByteArrayLiteral("out"));
1686 m_subst_core << qMakePair(QByteArrayLiteral("$FVARYING"), QByteArrayLiteral("in"));
1687 m_subst_core << qMakePair(QByteArrayLiteral("$TEXTURE2D"), QByteArrayLiteral("texture"));
1688 m_subst_core << qMakePair(QByteArrayLiteral("$DECL_FRAGCOLOR"), QByteArrayLiteral("out vec4 fragColor;"));
1689 m_subst_core << qMakePair(QByteArrayLiteral("$FRAGCOLOR"), QByteArrayLiteral("fragColor"));
1690 m_subst_core << qMakePair(QByteArrayLiteral("$HIGHP "), y: QByteArray());
1691}
1692
1693GltfExporter::ProgramInfo *GltfExporter::chooseProgram(uint materialIndex)
1694{
1695 Importer::MaterialInfo matInfo = m_importer->materialInfo(materialIndex);
1696 const bool hasNormalTexture = matInfo.m_textures.contains(akey: "normal");
1697 const bool hasSpecularTexture = matInfo.m_textures.contains(akey: "specular");
1698 const bool hasDiffuseTexture = matInfo.m_textures.contains(akey: "diffuse");
1699
1700 if (hasNormalTexture && !m_importer->allMeshesForMaterialHaveTangents(materialIndex))
1701 qWarning() << "WARNING: Tangent vectors not exported while the material requires it. (hint: try -t)";
1702
1703 if (hasNormalTexture && hasSpecularTexture && hasDiffuseTexture) {
1704 if (opts.showLog)
1705 qDebug() << "Using program taking diffuse, specular, normal textures";
1706 return &m_progs[4];
1707 }
1708
1709 if (hasNormalTexture && hasDiffuseTexture) {
1710 if (opts.showLog)
1711 qDebug() << "Using program taking diffuse, normal textures";
1712 return &m_progs[3];
1713 }
1714
1715 if (hasSpecularTexture && hasDiffuseTexture) {
1716 if (opts.showLog)
1717 qDebug() << "Using program taking diffuse, specular textures";
1718 return &m_progs[2];
1719 }
1720
1721 if (hasDiffuseTexture) {
1722 if (opts.showLog)
1723 qDebug() << "Using program taking diffuse texture";
1724 return &m_progs[1];
1725 }
1726
1727 if (opts.showLog)
1728 qDebug() << "Using program without textures";
1729 return &m_progs[0];
1730}
1731
1732QString GltfExporter::exportNode(const Importer::Node *n, QJsonObject &nodes)
1733{
1734 QJsonObject node;
1735 node["name"] = n->name;
1736 QJsonArray children;
1737 for (const Importer::Node *c : n->children) {
1738 if (nodeIsUseful(n: c))
1739 children << exportNode(n: c, nodes);
1740 }
1741 node["children"] = children;
1742 QJsonArray matrix;
1743 const float *mtxp = n->transformation.constData();
1744 for (int j = 0; j < 16; ++j)
1745 matrix.append(value: *mtxp++);
1746 node["matrix"] = matrix;
1747 QJsonArray meshList;
1748 for (int j = 0; j < n->meshes.count(); ++j)
1749 meshList.append(value: m_importer->meshInfo(meshIndex: n->meshes[j]).name);
1750 if (!meshList.isEmpty()) {
1751 node["meshes"] = meshList;
1752 } else {
1753 QHash<QString, Importer::CameraInfo> cam = m_importer->cameraInfo();
1754 if (cam.contains(akey: n->name))
1755 node["camera"] = cam[n->name].name;
1756 }
1757
1758 nodes[n->uniqueName] = node;
1759 return n->uniqueName;
1760}
1761
1762static inline QJsonArray col2jsvec(const QVector<float> &color, bool alpha = false)
1763{
1764 QJsonArray arr;
1765 arr << color[0] << color[1] << color[2];
1766 if (alpha)
1767 arr << color[3];
1768 return arr;
1769}
1770
1771static inline QJsonArray vec2jsvec(const QVector<float> &v)
1772{
1773 QJsonArray arr;
1774 for (int i = 0; i < v.count(); ++i)
1775 arr << v[i];
1776 return arr;
1777}
1778
1779static inline void promoteColorsToRGBA(QJsonObject *obj)
1780{
1781 QJsonObject::iterator it = obj->begin(), itEnd = obj->end();
1782 while (it != itEnd) {
1783 QJsonArray arr = it.value().toArray();
1784 if (arr.count() == 3) {
1785 const QString key = it.key();
1786 if (key == QStringLiteral("ambient")
1787 || key == QStringLiteral("diffuse")
1788 || key == QStringLiteral("specular")) {
1789 arr.append(value: 1);
1790 *it = arr;
1791 }
1792 }
1793 ++it;
1794 }
1795}
1796
1797void GltfExporter::exportMaterials(QJsonObject &materials, QHash<QString, QString> *textureNameMap)
1798{
1799 for (uint i = 0; i < m_importer->materialCount(); ++i) {
1800 Importer::MaterialInfo matInfo = m_importer->materialInfo(materialIndex: i);
1801 QJsonObject material;
1802 material["name"] = matInfo.originalName;
1803
1804 bool opaque = true;
1805 QJsonObject vals;
1806 for (QHash<QByteArray, QString>::const_iterator it = matInfo.m_textures.constBegin(); it != matInfo.m_textures.constEnd(); ++it) {
1807 if (!textureNameMap->contains(akey: it.value()))
1808 textureNameMap->insert(akey: it.value(), avalue: newTextureName());
1809 QByteArray key = it.key();
1810 if (key == QByteArrayLiteral("normal")) // avoid clashing with the vertex normals
1811 key = QByteArrayLiteral("normalmap");
1812 // alpha is supported for diffuse textures, but have to check the image data to decide if blending is needed
1813 if (key == QByteArrayLiteral("diffuse")) {
1814 QString imgFn = opts.outDir + it.value();
1815 if (m_imageHasAlpha.contains(akey: imgFn)) {
1816 if (m_imageHasAlpha[imgFn])
1817 opaque = false;
1818 } else {
1819#ifdef HAS_QIMAGE
1820 QImage img(imgFn);
1821 if (!img.isNull()) {
1822 if (img.hasAlphaChannel()) {
1823 for (int y = 0; opaque && y < img.height(); ++y)
1824 for (int x = 0; opaque && x < img.width(); ++x)
1825 if (qAlpha(img.pixel(x, y)) < 255)
1826 opaque = false;
1827 }
1828 m_imageHasAlpha[imgFn] = !opaque;
1829 } else {
1830 qWarning() << "WARNING: Cannot determine presence of alpha for" << imgFn;
1831 }
1832#else
1833 qWarning() << "WARNING: No image support, assuming all textures are opaque";
1834#endif
1835 }
1836 }
1837 vals[key] = textureNameMap->value(akey: it.value());
1838 }
1839 for (QHash<QByteArray, float>::const_iterator it = matInfo.m_values.constBegin();
1840 it != matInfo.m_values.constEnd(); ++it) {
1841 if (vals.contains(key: it.key()))
1842 continue;
1843 vals[it.key()] = it.value();
1844 }
1845 for (QHash<QByteArray, QVector<float> >::const_iterator it = matInfo.m_colors.constBegin();
1846 it != matInfo.m_colors.constEnd(); ++it) {
1847 if (vals.contains(key: it.key()))
1848 continue;
1849 // alpha is supported for the diffuse color. < 1 will enable blending.
1850 const bool alpha = it.key() == QByteArrayLiteral("diffuse");
1851 if (alpha && it.value()[3] < 1.0f)
1852 opaque = false;
1853 vals[it.key()] = col2jsvec(color: it.value(), alpha);
1854 }
1855 if (opts.shaders)
1856 material["values"] = vals;
1857
1858 ProgramInfo *prog = chooseProgram(materialIndex: i);
1859 TechniqueInfo techniqueInfo;
1860 bool needsNewTechnique = true;
1861 for (int j = 0; j < m_techniques.count(); ++j) {
1862 if (m_techniques[j].prog == prog) {
1863 techniqueInfo = m_techniques[j];
1864 needsNewTechnique = opaque != techniqueInfo.opaque;
1865 }
1866 if (!needsNewTechnique)
1867 break;
1868 }
1869 if (needsNewTechnique) {
1870 QString techniqueName = newTechniqueName();
1871 techniqueInfo = TechniqueInfo(techniqueName, opaque, prog);
1872 m_techniques.append(t: techniqueInfo);
1873 m_usedPrograms.insert(value: prog);
1874 }
1875
1876 if (opts.shaders) {
1877 if (opts.showLog)
1878 qDebug().noquote() << "Material #" << i << "->" << techniqueInfo.name;
1879
1880 material["technique"] = techniqueInfo.name;
1881 if (opts.genCore) {
1882 material["techniqueCore"] = techniqueInfo.coreName;
1883 material["techniqueGL2"] = techniqueInfo.gl2Name;
1884 }
1885 }
1886
1887 if (opts.commonMat) {
1888 // The built-in shaders we output are of little use in practice.
1889 // Ideally we want Qt3D's own standard materials in order to have our
1890 // models participate in lighting for example. To achieve this, output
1891 // a KHR_materials_common block which Qt3D's loader will recognize and
1892 // prefer over the shader-based techniques.
1893 if (!prog->commonTechniqueName.isEmpty()) {
1894 QJsonObject commonMat;
1895 commonMat["technique"] = prog->commonTechniqueName;
1896 // Set the values as-is. "normalmap" is our own extension, not in the spec.
1897 // However, RGB colors have to be promoted to RGBA since the spec uses
1898 // vec4, and all types are pre-defined for common material values.
1899 promoteColorsToRGBA(obj: &vals);
1900 commonMat["values"] = vals;
1901 if (!opaque)
1902 commonMat["transparent"] = true;
1903 QJsonObject extensions;
1904 extensions["KHR_materials_common"] = commonMat;
1905 material["extensions"] = extensions;
1906 }
1907 }
1908
1909 materials[matInfo.name] = material;
1910 }
1911}
1912
1913void GltfExporter::writeShader(const QString &src, const QString &dst, const QVector<QPair<QByteArray, QByteArray> > &substTab)
1914{
1915 for (const Shader shader : shaders) {
1916 QByteArray name = src.toUtf8();
1917 if (!qstrcmp(str1: shader.name, str2: name.constData())) {
1918 QString outfn = opts.outDir + dst;
1919 QFile outf(outfn);
1920 if (outf.open(flags: QIODevice::WriteOnly | QIODevice::Truncate)) {
1921 m_files.insert(value: QFileInfo(outf.fileName()).fileName());
1922 if (opts.showLog)
1923 qDebug() << "Writing" << outfn;
1924 const auto lines = QString::fromUtf8(str: shader.text).split(sep: '\n');
1925 for (QString line : lines) {
1926 for (const auto &subst : substTab)
1927 line.replace(before: subst.first, after: subst.second);
1928 line += QStringLiteral("\n");
1929 outf.write(data: line.toUtf8());
1930 }
1931 }
1932 return;
1933 }
1934 }
1935 qWarning() << "ERROR: No shader found for" << src;
1936}
1937
1938void GltfExporter::exportParameter(QJsonObject &dst, const QVector<ProgramInfo::Param> &params)
1939{
1940 for (const ProgramInfo::Param &param : params) {
1941 QJsonObject parameter;
1942 parameter["type"] = int(param.type);
1943 if (!param.semantic.isEmpty())
1944 parameter["semantic"] = param.semantic;
1945 if (param.name == QStringLiteral("lightIntensity"))
1946 parameter["value"] = QJsonArray() << 1 << 1 << 1;
1947 if (param.name == QStringLiteral("lightPosition"))
1948 parameter["value"] = QJsonArray() << 0 << 0 << 0 << 1;
1949 dst[param.name] = parameter;
1950 }
1951}
1952
1953namespace {
1954struct ProgramNames
1955{
1956 QString name;
1957 QString coreName;
1958};
1959}
1960
1961void GltfExporter::exportTechniques(QJsonObject &obj, const QString &basename)
1962{
1963 if (!opts.shaders)
1964 return;
1965
1966 QJsonObject shaders;
1967 QHash<QString, QString> shaderMap;
1968 for (ProgramInfo *prog : qAsConst(t&: m_usedPrograms)) {
1969 QString newName;
1970 if (!shaderMap.contains(akey: prog->vertShader)) {
1971 QJsonObject vertexShader;
1972 vertexShader["type"] = 35633;
1973 if (newName.isEmpty())
1974 newName = newShaderName();
1975 QString key = basename + QStringLiteral("_") + newName + QStringLiteral("_v");
1976 QString fn = QString(QStringLiteral("%1.vert")).arg(a: key);
1977 vertexShader["uri"] = fn;
1978 writeShader(src: prog->vertShader, dst: fn, substTab: m_subst_es2);
1979 if (opts.genCore) {
1980 QJsonObject coreVertexShader;
1981 QString coreKey = QString(QStringLiteral("%1_core").arg(a: key));
1982 fn = QString(QStringLiteral("%1.vert")).arg(a: coreKey);
1983 coreVertexShader["type"] = 35633;
1984 coreVertexShader["uri"] = fn;
1985 writeShader(src: prog->vertShader, dst: fn, substTab: m_subst_core);
1986 shaders[coreKey] = coreVertexShader;
1987 shaderMap.insert(akey: QString(prog->vertShader + QStringLiteral("_core")), avalue: coreKey);
1988 }
1989 shaders[key] = vertexShader;
1990 shaderMap.insert(akey: prog->vertShader, avalue: key);
1991 }
1992 if (!shaderMap.contains(akey: prog->fragShader)) {
1993 QJsonObject fragmentShader;
1994 fragmentShader["type"] = 35632;
1995 if (newName.isEmpty())
1996 newName = newShaderName();
1997 QString key = basename + QStringLiteral("_") + newName + QStringLiteral("_f");
1998 QString fn = QString(QStringLiteral("%1.frag")).arg(a: key);
1999 fragmentShader["uri"] = fn;
2000 writeShader(src: prog->fragShader, dst: fn, substTab: m_subst_es2);
2001 if (opts.genCore) {
2002 QJsonObject coreFragmentShader;
2003 QString coreKey = QString(QStringLiteral("%1_core").arg(a: key));
2004 fn = QString(QStringLiteral("%1.frag")).arg(a: coreKey);
2005 coreFragmentShader["type"] = 35632;
2006 coreFragmentShader["uri"] = fn;
2007 writeShader(src: prog->fragShader, dst: fn, substTab: m_subst_core);
2008 shaders[coreKey] = coreFragmentShader;
2009 shaderMap.insert(akey: QString(prog->fragShader + QStringLiteral("_core")), avalue: coreKey);
2010 }
2011 shaders[key] = fragmentShader;
2012 shaderMap.insert(akey: prog->fragShader, avalue: key);
2013 }
2014 }
2015 obj["shaders"] = shaders;
2016
2017 QJsonObject programs;
2018 QHash<const ProgramInfo *, ProgramNames> programMap;
2019 for (const ProgramInfo *prog : qAsConst(t&: m_usedPrograms)) {
2020 QJsonObject program;
2021 program["vertexShader"] = shaderMap[prog->vertShader];
2022 program["fragmentShader"] = shaderMap[prog->fragShader];
2023 QJsonArray attrs;
2024 for (const ProgramInfo::Param &param : prog->attributes) {
2025 attrs << param.nameInShader;
2026 }
2027 program["attributes"] = attrs;
2028 QString programName = newProgramName();
2029 programMap[prog].name = programName;
2030 programs[programMap[prog].name] = program;
2031 if (opts.genCore) {
2032 program["vertexShader"] = shaderMap[QString(prog->vertShader + QLatin1String("_core"))];
2033 program["fragmentShader"] = shaderMap[QString(prog->fragShader + QLatin1String("_core"))];
2034 QJsonArray attrs;
2035 for (const ProgramInfo::Param &param : prog->attributes) {
2036 attrs << param.nameInShader;
2037 }
2038 program["attributes"] = attrs;
2039 programMap[prog].coreName = programName + QLatin1String("_core");
2040 programs[programMap[prog].coreName] = program;
2041 }
2042 }
2043 obj["programs"] = programs;
2044
2045 QJsonObject techniques;
2046 for (const TechniqueInfo &techniqueInfo : qAsConst(t&: m_techniques)) {
2047 QJsonObject technique;
2048 QJsonObject parameters;
2049 const ProgramInfo *prog = techniqueInfo.prog;
2050 exportParameter(dst&: parameters, params: prog->attributes);
2051 exportParameter(dst&: parameters, params: prog->uniforms);
2052 technique["parameters"] = parameters;
2053 technique["program"] = programMap[prog].name;
2054 QJsonObject progAttrs;
2055 for (const ProgramInfo::Param &param : prog->attributes) {
2056 progAttrs[param.nameInShader] = param.name;
2057 }
2058 technique["attributes"] = progAttrs;
2059 QJsonObject progUniforms;
2060 for (const ProgramInfo::Param &param : prog->uniforms) {
2061 progUniforms[param.nameInShader] = param.name;
2062 }
2063 technique["uniforms"] = progUniforms;
2064 QJsonObject states;
2065 QJsonArray enabledStates;
2066 enabledStates << GLT_DEPTH_TEST << GLT_CULL_FACE;
2067 if (!techniqueInfo.opaque) {
2068 enabledStates << GLT_BLEND;
2069 QJsonObject funcs;
2070 // GL_ONE, GL_ONE_MINUS_SRC_ALPHA
2071 funcs["blendFuncSeparate"] = QJsonArray() << 1 << 771 << 1 << 771;
2072 states["functions"] = funcs;
2073 }
2074 states["enable"] = enabledStates;
2075 technique["states"] = states;
2076 techniques[techniqueInfo.name] = technique;
2077
2078 if (opts.genCore) {
2079 //GL2 (same as ES2)
2080 techniques[techniqueInfo.gl2Name] = technique;
2081
2082 //Core
2083 technique["program"] = programMap[prog].coreName;
2084 techniques[techniqueInfo.coreName] = technique;
2085 }
2086 }
2087 obj["techniques"] = techniques;
2088}
2089
2090void GltfExporter::exportAnimations(QJsonObject &obj,
2091 QVector<Importer::BufferInfo> &bufList,
2092 QVector<Importer::MeshInfo::BufferView> &bvList,
2093 QVector<Importer::MeshInfo::Accessor> &accList)
2094{
2095 const auto animationInfos = m_importer->animations();
2096 if (animationInfos.empty()) {
2097 obj["animations"] = QJsonObject();
2098 return;
2099 }
2100
2101 QString bvName = newBufferViewName();
2102 QByteArray extraData;
2103
2104 int sz = 0;
2105 for (const Importer::AnimationInfo &ai : animationInfos)
2106 sz += ai.keyFrames.count() * (1 + 3 + 4 + 3) * sizeof(float);
2107 extraData.resize(size: sz);
2108
2109 float *base = reinterpret_cast<float *>(extraData.data());
2110 float *p = base;
2111
2112 QJsonObject animations;
2113 for (const Importer::AnimationInfo &ai : animationInfos) {
2114 QJsonObject animation;
2115 animation["name"] = ai.name;
2116 animation["count"] = ai.keyFrames.count();
2117 QJsonObject samplers;
2118 QJsonArray channels;
2119
2120 if (ai.hasTranslation) {
2121 QJsonObject sampler;
2122 sampler["input"] = QStringLiteral("TIME");
2123 sampler["interpolation"] = QStringLiteral("LINEAR");
2124 sampler["output"] = QStringLiteral("translation");
2125 samplers["sampler_translation"] = sampler;
2126 QJsonObject channel;
2127 channel["sampler"] = QStringLiteral("sampler_translation");
2128 QJsonObject target;
2129 target["id"] = ai.targetNode;
2130 target["path"] = QStringLiteral("translation");
2131 channel["target"] = target;
2132 channels << channel;
2133 }
2134 if (ai.hasRotation) {
2135 QJsonObject sampler;
2136 sampler["input"] = QStringLiteral("TIME");
2137 sampler["interpolation"] = QStringLiteral("LINEAR");
2138 sampler["output"] = QStringLiteral("rotation");
2139 samplers["sampler_rotation"] = sampler;
2140 QJsonObject channel;
2141 channel["sampler"] = QStringLiteral("sampler_rotation");
2142 QJsonObject target;
2143 target["id"] = ai.targetNode;
2144 target["path"] = QStringLiteral("rotation");
2145 channel["target"] = target;
2146 channels << channel;
2147 }
2148 if (ai.hasScale) {
2149 QJsonObject sampler;
2150 sampler["input"] = QStringLiteral("TIME");
2151 sampler["interpolation"] = QStringLiteral("LINEAR");
2152 sampler["output"] = QStringLiteral("scale");
2153 samplers["sampler_scale"] = sampler;
2154 QJsonObject channel;
2155 channel["sampler"] = QStringLiteral("sampler_scale");
2156 QJsonObject target;
2157 target["id"] = ai.targetNode;
2158 target["path"] = QStringLiteral("scale");
2159 channel["target"] = target;
2160 channels << channel;
2161 }
2162
2163 animation["samplers"] = samplers;
2164 animation["channels"] = channels;
2165 QJsonObject parameters;
2166
2167 // Multiple animations sharing the same data should ideally use the
2168 // same accessors. This we unfortunately cannot do due to assimp's/our
2169 // own data structures so everything will get its own accessor and data
2170 // for now.
2171
2172 Importer::MeshInfo::Accessor acc;
2173 acc.name = newAccessorName();
2174 acc.bufferView = bvName;
2175 acc.count = ai.keyFrames.count();
2176 acc.componentType = GLT_FLOAT;
2177 acc.type = QStringLiteral("SCALAR");
2178 acc.offset = uint((p - base) * sizeof(float));
2179 for (const Importer::KeyFrame &kf : ai.keyFrames)
2180 *p++ = kf.t;
2181 parameters["TIME"] = acc.name;
2182 accList << acc;
2183
2184 if (ai.hasTranslation) {
2185 acc.name = newAccessorName();
2186 acc.componentType = GLT_FLOAT;
2187 acc.type = QStringLiteral("VEC3");
2188 acc.offset = uint((p - base) * sizeof(float));
2189 QVector<float> lastV;
2190 for (const Importer::KeyFrame &kf : ai.keyFrames) {
2191 const QVector<float> *v = kf.transValid ? &kf.trans : &lastV;
2192 *p++ = v->at(i: 0);
2193 *p++ = v->at(i: 1);
2194 *p++ = v->at(i: 2);
2195 if (kf.transValid)
2196 lastV = *v;
2197 }
2198 parameters["translation"] = acc.name;
2199 accList << acc;
2200 }
2201 if (ai.hasRotation) {
2202 acc.name = newAccessorName();
2203 acc.componentType = GLT_FLOAT;
2204 acc.type = QStringLiteral("VEC4");
2205 acc.offset = uint((p - base) * sizeof(float));
2206 QVector<float> lastV;
2207 for (const Importer::KeyFrame &kf : ai.keyFrames) {
2208 const QVector<float> *v = kf.rotValid ? &kf.rot : &lastV;
2209 *p++ = v->at(i: 1); // x
2210 *p++ = v->at(i: 2); // y
2211 *p++ = v->at(i: 3); // z
2212 *p++ = v->at(i: 0); // w
2213 if (kf.rotValid)
2214 lastV = *v;
2215 }
2216 parameters["rotation"] = acc.name;
2217 accList << acc;
2218 }
2219 if (ai.hasScale) {
2220 acc.name = newAccessorName();
2221 acc.componentType = GLT_FLOAT;
2222 acc.type = QStringLiteral("VEC3");
2223 acc.offset = uint((p - base) * sizeof(float));
2224 QVector<float> lastV;
2225 for (const Importer::KeyFrame &kf : ai.keyFrames) {
2226 const QVector<float> *v = kf.scaleValid ? &kf.scale : &lastV;
2227 *p++ = v->at(i: 0);
2228 *p++ = v->at(i: 1);
2229 *p++ = v->at(i: 2);
2230 if (kf.scaleValid)
2231 lastV = *v;
2232 }
2233 parameters["scale"] = acc.name;
2234 accList << acc;
2235 }
2236 animation["parameters"] = parameters;
2237
2238 animations[newAnimationName()] = animation;
2239 }
2240 obj["animations"] = animations;
2241
2242 // Now all the key frame data is in extraData. Append it to the first buffer
2243 // and create a single buffer view for it.
2244 if (!extraData.isEmpty()) {
2245 if (bufList.isEmpty()) {
2246 Importer::BufferInfo b;
2247 b.name = QStringLiteral("buf");
2248 bufList << b;
2249 }
2250 Importer::BufferInfo &buf(bufList[0]);
2251 Importer::MeshInfo::BufferView bv;
2252 bv.name = bvName;
2253 bv.offset = buf.data.size();
2254 bv.length = uint((p - base) * sizeof(float));
2255 bv.componentType = GLT_FLOAT;
2256 bvList << bv;
2257 extraData.resize(size: bv.length);
2258 buf.data += extraData;
2259 if (opts.showLog)
2260 qDebug().noquote() << "Animation data in buffer uses" << extraData.size() << "bytes";
2261 }
2262}
2263
2264void GltfExporter::save(const QString &inputFilename)
2265{
2266 if (opts.showLog)
2267 qDebug() << "Exporting";
2268
2269 m_files.clear();
2270 m_techniques.clear();
2271 m_usedPrograms.clear();
2272
2273 QFile f;
2274 QString basename = QFileInfo(inputFilename).baseName();
2275 QString bufNameTempl = basename + QStringLiteral("_%1.bin");
2276
2277 copyExternalTextures(inputFilename);
2278 exportEmbeddedTextures();
2279 compressTextures();
2280
2281 m_obj = QJsonObject();
2282
2283 QVector<Importer::BufferInfo> bufList = m_importer->buffers();
2284 QVector<Importer::MeshInfo::BufferView> bvList = m_importer->bufferViews();
2285 QVector<Importer::MeshInfo::Accessor> accList = m_importer->accessors();
2286
2287 // Animations add data to the buffer so process them first.
2288 exportAnimations(obj&: m_obj, bufList, bvList, accList);
2289
2290 QJsonObject asset;
2291 asset["generator"] = QString(QStringLiteral("qgltf %1")).arg(a: QCoreApplication::applicationVersion());
2292 asset["version"] = QStringLiteral("1.0");
2293 asset["premultipliedAlpha"] = true;
2294 m_obj["asset"] = asset;
2295
2296 for (int i = 0; i < bufList.count(); ++i) {
2297 QString bufName = bufNameTempl.arg(a: i + 1);
2298 f.setFileName(opts.outDir + bufName);
2299 if (opts.showLog)
2300 qDebug().noquote() << (opts.compress ? "Writing (compressed)" : "Writing") << (opts.outDir + bufName);
2301 if (f.open(flags: QIODevice::WriteOnly | QIODevice::Truncate)) {
2302 m_files.insert(value: QFileInfo(f.fileName()).fileName());
2303 QByteArray data = bufList[i].data;
2304 if (opts.compress)
2305 data = qCompress(data);
2306 f.write(data);
2307 f.close();
2308 }
2309 }
2310
2311 QJsonObject buffers;
2312 for (int i = 0; i < bufList.count(); ++i) {
2313 QJsonObject buffer;
2314 buffer["byteLength"] = bufList[i].data.size();
2315 buffer["type"] = QStringLiteral("arraybuffer");
2316 buffer["uri"] = bufNameTempl.arg(a: i + 1);
2317 if (opts.compress)
2318 buffer["compression"] = QStringLiteral("Qt");
2319 buffers[bufList[i].name] = buffer;
2320 }
2321 m_obj["buffers"] = buffers;
2322
2323 QJsonObject bufferViews;
2324 for (const Importer::MeshInfo::BufferView &bv : qAsConst(t&: bvList)) {
2325 QJsonObject bufferView;
2326 bufferView["buffer"] = bufList[bv.bufIndex].name;
2327 bufferView["byteLength"] = int(bv.length);
2328 bufferView["byteOffset"] = int(bv.offset);
2329 if (bv.target)
2330 bufferView["target"] = int(bv.target);
2331 bufferViews[bv.name] = bufferView;
2332 }
2333 m_obj["bufferViews"] = bufferViews;
2334
2335 QJsonObject accessors;
2336 for (const Importer::MeshInfo::Accessor &acc : qAsConst(t&: accList)) {
2337 QJsonObject accessor;
2338 accessor["bufferView"] = acc.bufferView;
2339 accessor["byteOffset"] = int(acc.offset);
2340 accessor["byteStride"] = int(acc.stride);
2341 accessor["count"] = int(acc.count);
2342 accessor["componentType"] = int(acc.componentType);
2343 accessor["type"] = acc.type;
2344 if (!acc.minVal.isEmpty() && !acc.maxVal.isEmpty()) {
2345 accessor["min"] = vec2jsvec(v: acc.minVal);
2346 accessor["max"] = vec2jsvec(v: acc.maxVal);
2347 }
2348 accessors[acc.name] = accessor;
2349 }
2350 m_obj["accessors"] = accessors;
2351
2352 QJsonObject meshes;
2353 for (uint i = 0; i < m_importer->meshCount(); ++i) {
2354 const Importer::MeshInfo meshInfo = m_importer->meshInfo(meshIndex: i);
2355 QJsonObject mesh;
2356 mesh["name"] = meshInfo.originalName;
2357 QJsonArray prims;
2358 QJsonObject prim;
2359 prim["mode"] = 4; // triangles
2360 QJsonObject attrs;
2361 for (const Importer::MeshInfo::Accessor &acc : meshInfo.accessors) {
2362 if (acc.usage != QStringLiteral("INDEX"))
2363 attrs[acc.usage] = acc.name;
2364 else
2365 prim["indices"] = acc.name;
2366 }
2367 prim["attributes"] = attrs;
2368 prim["material"] = m_importer->materialInfo(materialIndex: meshInfo.materialIndex).name;
2369 prims.append(value: prim);
2370 mesh["primitives"] = prims;
2371 meshes[meshInfo.name] = mesh;
2372 }
2373 m_obj["meshes"] = meshes;
2374
2375 QJsonObject cameras;
2376 const auto cameraInfos = m_importer->cameraInfo();
2377 for (const Importer::CameraInfo &camInfo : cameraInfos) {
2378 QJsonObject camera;
2379 QJsonObject persp;
2380 persp["aspect_ratio"] = camInfo.aspectRatio;
2381 persp["yfov"] = camInfo.yfov;
2382 persp["znear"] = camInfo.znear;
2383 persp["zfar"] = camInfo.zfar;
2384 camera["perspective"] = persp;
2385 camera["type"] = QStringLiteral("perspective");
2386 cameras[camInfo.name] = camera;
2387 }
2388 m_obj["cameras"] = cameras;
2389
2390 QJsonArray sceneNodes;
2391 QJsonObject nodes;
2392 for (const Importer::Node *n : qAsConst(t: m_importer->rootNode()->children)) {
2393 if (nodeIsUseful(n))
2394 sceneNodes << exportNode(n, nodes);
2395 }
2396 m_obj["nodes"] = nodes;
2397
2398 QJsonObject scenes;
2399 QJsonObject defaultScene;
2400 defaultScene["nodes"] = sceneNodes;
2401 scenes["defaultScene"] = defaultScene;
2402 m_obj["scenes"] = scenes;
2403 m_obj["scene"] = QStringLiteral("defaultScene");
2404
2405 QJsonObject materials;
2406 QHash<QString, QString> textureNameMap;
2407 exportMaterials(materials, textureNameMap: &textureNameMap);
2408 m_obj["materials"] = materials;
2409
2410 QJsonObject textures;
2411 QHash<QString, QString> imageMap; // uri -> key
2412 for (QHash<QString, QString>::const_iterator it = textureNameMap.constBegin(); it != textureNameMap.constEnd(); ++it) {
2413 QJsonObject texture;
2414 if (!imageMap.contains(akey: it.key()))
2415 imageMap[it.key()] = newImageName();
2416 texture["source"] = imageMap[it.key()];
2417 texture["format"] = 0x1908; // RGBA
2418 const bool compressed = m_compressedTextures.contains(akey: it.key());
2419 texture["internalFormat"] = !compressed ? 0x1908 : 0x8D64; // RGBA / ETC1
2420 texture["sampler"] = !compressed ? QStringLiteral("sampler_mip_rep") : QStringLiteral("sampler_nonmip_rep");
2421 texture["target"] = 3553; // TEXTURE_2D
2422 texture["type"] = 5121; // UNSIGNED_BYTE
2423 textures[it.value()] = texture;
2424 }
2425 m_obj["textures"] = textures;
2426
2427 QJsonObject images;
2428 for (QHash<QString, QString>::const_iterator it = imageMap.constBegin(); it != imageMap.constEnd(); ++it) {
2429 QJsonObject image;
2430 image["uri"] = m_compressedTextures.contains(akey: it.key()) ? m_compressedTextures[it.key()] : it.key();
2431 images[it.value()] = image;
2432 }
2433 m_obj["images"] = images;
2434
2435 QJsonObject samplers;
2436 QJsonObject sampler;
2437 sampler["magFilter"] = 9729; // LINEAR
2438 sampler["minFilter"] = 9987; // LINEAR_MIPMAP_LINEAR
2439 sampler["wrapS"] = 10497; // REPEAT
2440 sampler["wrapT"] = 10497;
2441 samplers["sampler_mip_rep"] = sampler;
2442 // Compressed textures may not support mipmapping with GLES.
2443 if (!m_compressedTextures.isEmpty()) {
2444 sampler["minFilter"] = 9729; // LINEAR
2445 samplers["sampler_nonmip_rep"] = sampler;
2446 }
2447 m_obj["samplers"] = samplers;
2448
2449 exportTechniques(obj&: m_obj, basename);
2450
2451 m_doc.setObject(m_obj);
2452
2453 QString gltfName = opts.outDir + basename + QStringLiteral(".qgltf");
2454 f.setFileName(gltfName);
2455
2456#ifndef QT_BOOTSTRAPPED
2457 if (opts.showLog)
2458 qDebug().noquote() << (opts.genBin ? "Writing (binary JSON)" : "Writing") << gltfName;
2459
2460 const QIODevice::OpenMode openMode = opts.genBin
2461 ? (QIODevice::WriteOnly | QIODevice::Truncate)
2462 : (QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text);
2463QT_WARNING_PUSH
2464QT_WARNING_DISABLE_DEPRECATED
2465 const QByteArray json = opts.genBin
2466 ? m_doc.toBinaryData()
2467 : m_doc.toJson(format: opts.compact ? QJsonDocument::Compact : QJsonDocument::Indented);
2468QT_WARNING_POP
2469#else
2470 if (opts.showLog)
2471 qDebug().noquote() << "Writing" << gltfName;
2472
2473 const QIODevice::OpenMode openMode
2474 = QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text;
2475 const QByteArray json
2476 = m_doc.toJson(opts.compact ? QJsonDocument::Compact : QJsonDocument::Indented);
2477#endif
2478
2479 if (f.open(flags: openMode)) {
2480 m_files.insert(value: QFileInfo(f.fileName()).fileName());
2481 f.write(data: json);
2482 f.close();
2483 }
2484
2485 QString qrcName = opts.outDir + basename + QStringLiteral(".qrc");
2486 f.setFileName(qrcName);
2487 if (opts.showLog)
2488 qDebug().noquote() << "Writing" << qrcName;
2489 if (f.open(flags: QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
2490 QByteArray pre = "<RCC><qresource prefix=\"/models\">\n";
2491 QByteArray post = "</qresource></RCC>\n";
2492 f.write(data: pre);
2493 for (const QString &file : qAsConst(t&: m_files)) {
2494 QString line = QString(QStringLiteral(" <file>%1</file>\n")).arg(a: file);
2495 f.write(data: line.toUtf8());
2496 }
2497 f.write(data: post);
2498 f.close();
2499 }
2500
2501 if (opts.showLog)
2502 qDebug() << "Done\n";
2503}
2504
2505static const char *description =
2506 "qgltf uses Assimp to import a variety of 3D model formats "
2507 "and export it into fast-to-load, optimized glTF "
2508 "assets embedded into Qt resource files.\n\n"
2509 "Note: this tool should typically not be invoked directly. Instead, "
2510 "let qmake manage it based on QT3D_MODELS in the .pro file.\n\n"
2511 "For standard Qt 3D usage the recommended options are -b -S.";
2512
2513int main(int argc, char **argv)
2514{
2515 QCoreApplication app(argc, argv);
2516 app.setApplicationVersion(QStringLiteral("0.2"));
2517 app.setApplicationName(QStringLiteral("Qt glTF converter"));
2518
2519 QCommandLineParser cmdLine;
2520 cmdLine.addHelpOption();
2521 cmdLine.addVersionOption();
2522 cmdLine.setApplicationDescription(QString::fromUtf8(str: description));
2523 QCommandLineOption outDirOpt(QStringLiteral("d"), QStringLiteral("Place all output data into <dir>"), QStringLiteral("dir"));
2524 cmdLine.addOption(commandLineOption: outDirOpt);
2525#ifndef QT_BOOTSTRAPPED
2526 QCommandLineOption binOpt(QStringLiteral("b"), QStringLiteral("Store binary JSON data in the .qgltf file"));
2527 cmdLine.addOption(commandLineOption: binOpt);
2528#endif
2529 QCommandLineOption compactOpt(QStringLiteral("m"), QStringLiteral("Store compact JSON in the .qgltf file"));
2530 cmdLine.addOption(commandLineOption: compactOpt);
2531 QCommandLineOption compOpt(QStringLiteral("c"), QStringLiteral("qCompress() vertex/index data in the .bin file"));
2532 cmdLine.addOption(commandLineOption: compOpt);
2533 QCommandLineOption tangentOpt(QStringLiteral("t"), QStringLiteral("Generate tangent vectors"));
2534 cmdLine.addOption(commandLineOption: tangentOpt);
2535 QCommandLineOption nonInterleavedOpt(QStringLiteral("n"), QStringLiteral("Use non-interleaved buffer layout"));
2536 cmdLine.addOption(commandLineOption: nonInterleavedOpt);
2537 QCommandLineOption scaleOpt(QStringLiteral("e"), QStringLiteral("Scale vertices by the float scale factor <factor>"), QStringLiteral("factor"));
2538 cmdLine.addOption(commandLineOption: scaleOpt);
2539 QCommandLineOption coreOpt(QStringLiteral("g"), QStringLiteral("Generate OpenGL 3.2+ core profile shaders too"));
2540 cmdLine.addOption(commandLineOption: coreOpt);
2541 QCommandLineOption etc1Opt(QStringLiteral("1"), QStringLiteral("Generate ETC1 compressed textures by invoking etc1tool (PNG only)"));
2542 cmdLine.addOption(commandLineOption: etc1Opt);
2543 QCommandLineOption noCommonMatOpt(QStringLiteral("T"), QStringLiteral("Do not generate KHR_materials_common block"));
2544 cmdLine.addOption(commandLineOption: noCommonMatOpt);
2545 QCommandLineOption noShadersOpt(QStringLiteral("S"), QStringLiteral("Do not generate shaders/programs/techniques"));
2546 cmdLine.addOption(commandLineOption: noShadersOpt);
2547 QCommandLineOption silentOpt(QStringLiteral("s"), QStringLiteral("Silence debug output"));
2548 cmdLine.addOption(commandLineOption: silentOpt);
2549 cmdLine.process(app);
2550 opts.outDir = cmdLine.value(option: outDirOpt);
2551#ifndef QT_BOOTSTRAPPED
2552 opts.genBin = cmdLine.isSet(option: binOpt);
2553#endif
2554 opts.compact = cmdLine.isSet(option: compactOpt);
2555 opts.compress = cmdLine.isSet(option: compOpt);
2556 opts.genTangents = cmdLine.isSet(option: tangentOpt);
2557 opts.interleave = !cmdLine.isSet(option: nonInterleavedOpt);
2558 opts.scale = 1;
2559 if (cmdLine.isSet(option: scaleOpt)) {
2560 bool ok = false;
2561 float v;
2562 v = cmdLine.value(option: scaleOpt).toFloat(ok: &ok);
2563 if (ok)
2564 opts.scale = v;
2565 }
2566 opts.genCore = cmdLine.isSet(option: coreOpt);
2567 opts.texComp = cmdLine.isSet(option: etc1Opt) ? Options::ETC1 : Options::NoTextureCompression;
2568 opts.commonMat = !cmdLine.isSet(option: noCommonMatOpt);
2569 opts.shaders = !cmdLine.isSet(option: noShadersOpt);
2570 opts.showLog = !cmdLine.isSet(option: silentOpt);
2571 if (!opts.outDir.isEmpty()) {
2572 if (!opts.outDir.endsWith(c: '/'))
2573 opts.outDir.append(c: '/');
2574 QDir().mkpath(dirPath: opts.outDir);
2575 }
2576
2577 const auto fileNames = cmdLine.positionalArguments();
2578 if (fileNames.isEmpty())
2579 cmdLine.showHelp();
2580
2581 AssimpImporter importer;
2582 GltfExporter exporter(&importer);
2583 for (const QString &fn : fileNames) {
2584 if (!importer.load(filename: fn)) {
2585 qWarning() << "Failed to import" << fn;
2586 continue;
2587 }
2588 exporter.save(inputFilename: fn);
2589 }
2590
2591 return 0;
2592}
2593

source code of qt3d/tools/qgltf/qgltf.cpp