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 
66 class AssimpIOStream : public Assimp::IOStream
67 {
68 public:
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 
79 private:
80     QIODevice *m_device;
81 };
82 
83 class AssimpIOSystem : public Assimp::IOSystem
84 {
85 public:
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 
AssimpIOStream(QIODevice * device)92 AssimpIOStream::AssimpIOStream(QIODevice *device) :
93     m_device(device)
94 {
95     Q_ASSERT(m_device);
96 }
97 
~AssimpIOStream()98 AssimpIOStream::~AssimpIOStream()
99 {
100     delete m_device;
101 }
102 
Read(void * pvBuffer,size_t pSize,size_t pCount)103 size_t AssimpIOStream::Read(void *pvBuffer, size_t pSize, size_t pCount)
104 {
105     qint64 readBytes = m_device->read((char *)pvBuffer, pSize * pCount);
106     if (readBytes < 0)
107         qWarning() << Q_FUNC_INFO << " read failed";
108     return readBytes;
109 }
110 
Write(const void * pvBuffer,size_t pSize,size_t pCount)111 size_t AssimpIOStream::Write(const void *pvBuffer, size_t pSize, size_t pCount)
112 {
113     qint64 writtenBytes = m_device->write((char *)pvBuffer, pSize * pCount);
114     if (writtenBytes < 0)
115         qWarning() << Q_FUNC_INFO << " write failed";
116     return writtenBytes;
117 }
118 
Seek(size_t pOffset,aiOrigin pOrigin)119 aiReturn 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(seekPos)) {
129         qWarning() << Q_FUNC_INFO << " seek failed";
130         return aiReturn_FAILURE;
131     }
132     return aiReturn_SUCCESS;
133 }
134 
Tell() const135 size_t AssimpIOStream::Tell() const
136 {
137     return m_device->pos();
138 }
139 
FileSize() const140 size_t AssimpIOStream::FileSize() const
141 {
142     return m_device->size();
143 }
144 
Flush()145 void AssimpIOStream::Flush()
146 {
147     // we don't write via assimp
148 }
149 
openModeFromText(const char * name)150 static QIODevice::OpenMode openModeFromText(const char *name) noexcept
151 {
152     static const struct OpenModeMapping {
153         char name[2];
154         int mode;
155     } openModeMapping[] = {
156         { { 'r',   0 },  QIODevice::ReadOnly  },
157         { { 'r', '+' },  QIODevice::ReadWrite },
158         { { 'w',   0 },  QIODevice::WriteOnly | QIODevice::Truncate },
159         { { 'w', '+' },  QIODevice::ReadWrite | QIODevice::Truncate },
160         { { 'a',   0 },  QIODevice::WriteOnly | QIODevice::Append },
161         { { 'a', '+' },  QIODevice::ReadWrite | QIODevice::Append },
162         { { 'w', 'b' },  QIODevice::WriteOnly },
163         { { 'w', 't' },  QIODevice::WriteOnly | QIODevice::Text },
164         { { 'r', 'b' },  QIODevice::ReadOnly  },
165         { { 'r', 't' },  QIODevice::ReadOnly  | QIODevice::Text },
166     };
167 
168     for (auto e : openModeMapping) {
169         if (qstrncmp(e.name, name, sizeof(OpenModeMapping::name)) == 0)
170             return static_cast<QIODevice::OpenMode>(e.mode);
171     }
172     return QIODevice::NotOpen;
173 }
174 
Exists(const char * pFile) const175 bool AssimpIOSystem::Exists(const char *pFile) const
176 {
177     return QFileInfo::exists(QString::fromUtf8(pFile));
178 }
179 
getOsSeparator() const180 char AssimpIOSystem::getOsSeparator() const
181 {
182     return QDir::separator().toLatin1();
183 }
184 
Open(const char * pFile,const char * pMode)185 Assimp::IOStream *AssimpIOSystem::Open(const char *pFile, const char *pMode)
186 {
187     const QString fileName(QString::fromUtf8(pFile));
188     const QLatin1String cleanedMode = QLatin1String{pMode}.trimmed();
189 
190     if (const QIODevice::OpenMode openMode = openModeFromText(cleanedMode.data())) {
191         QScopedPointer<QFile> file(new QFile(fileName));
192         if (file->open(openMode))
193             return new AssimpIOStream(file.take());
194     }
195 
196     return nullptr;
197 }
198 
Close(Assimp::IOStream * pFile)199 void AssimpIOSystem::Close(Assimp::IOStream *pFile)
200 {
201     delete pFile;
202 }
203 
ai2qt(const aiString & str)204 static inline QString ai2qt(const aiString &str)
205 {
206     return QString::fromUtf8(str.data, int(str.length));
207 }
208 
ai2qt(const aiMatrix4x4 & matrix)209 static 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 
217 struct 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 
238 class Importer
239 {
240 public:
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 {
BufferViewImporter::MeshInfo::BufferView254             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 {
AccessorImporter::MeshInfo::Accessor264             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 {
EmbeddedTextureInfoImporter::EmbeddedTextureInfo309         EmbeddedTextureInfo() { }
310         QString name;
311 #ifdef HAS_QIMAGE
EmbeddedTextureInfoImporter::EmbeddedTextureInfo312         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 {
KeyFrameImporter::KeyFrame328         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 {
AnimationInfoImporter::AnimationInfo338         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 
352 protected:
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 };
364 QT_BEGIN_NAMESPACE
365 Q_DECLARE_TYPEINFO(Importer::BufferInfo,           Q_MOVABLE_TYPE);
366 Q_DECLARE_TYPEINFO(Importer::MeshInfo::BufferView, Q_MOVABLE_TYPE);
367 Q_DECLARE_TYPEINFO(Importer::MeshInfo::Accessor,   Q_MOVABLE_TYPE);
368 Q_DECLARE_TYPEINFO(Importer::MaterialInfo,         Q_MOVABLE_TYPE);
369 Q_DECLARE_TYPEINFO(Importer::CameraInfo,           Q_MOVABLE_TYPE);
370 Q_DECLARE_TYPEINFO(Importer::EmbeddedTextureInfo,  Q_MOVABLE_TYPE);
371 Q_DECLARE_TYPEINFO(Importer::Node,                 Q_COMPLEX_TYPE); // uses address as identity
372 Q_DECLARE_TYPEINFO(Importer::KeyFrame,             Q_MOVABLE_TYPE);
373 Q_DECLARE_TYPEINFO(Importer::AnimationInfo,        Q_MOVABLE_TYPE);
374 QT_END_NAMESPACE
375 
Importer()376 Importer::Importer()
377     : m_rootNode(nullptr)
378 {
379 }
380 
delNode(Importer::Node * n)381 void Importer::delNode(Importer::Node *n)
382 {
383     if (!n)
384         return;
385     for (Importer::Node *c : qAsConst(n->children))
386         delNode(c);
387     delete n;
388 }
389 
~Importer()390 Importer::~Importer()
391 {
392     delNode(m_rootNode);
393 }
394 
buffers() const395 QVector<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 
rootNode() const403 const Importer::Node *Importer::rootNode() const
404 {
405     return m_rootNode;
406 }
407 
allMeshesForMaterialHaveTangents(uint materialIndex) const408 bool 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 
bufferViews() const426 QVector<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 
accessors() const436 QVector<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 
meshCount() const446 uint Importer::meshCount() const
447 {
448     return m_meshInfo.count();
449 }
450 
meshInfo(uint meshIndex) const451 Importer::MeshInfo Importer::meshInfo(uint meshIndex) const
452 {
453     return m_meshInfo[meshIndex];
454 }
455 
materialCount() const456 uint Importer::materialCount() const
457 {
458     return m_materialInfo.count();
459 }
460 
materialInfo(uint materialIndex) const461 Importer::MaterialInfo Importer::materialInfo(uint materialIndex) const
462 {
463     return m_materialInfo[materialIndex];
464 }
465 
cameraInfo() const466 QHash<QString, Importer::CameraInfo> Importer::cameraInfo() const
467 {
468     return m_cameraInfo;
469 }
470 
externalTextures() const471 QSet<QString> Importer::externalTextures() const
472 {
473     return m_externalTextures;
474 }
475 
embeddedTextures() const476 QHash<QString, Importer::EmbeddedTextureInfo> Importer::embeddedTextures() const
477 {
478     return m_embeddedTextures;
479 }
480 
animations() const481 QVector<Importer::AnimationInfo> Importer::animations() const
482 {
483     return m_animations;
484 }
485 
findNode(const Node * root,const QString & originalName) const486 const 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(c, originalName);
492         if (cn)
493             return cn;
494     }
495     return nullptr;
496 }
497 
498 class AssimpImporter : public Importer
499 {
500 public:
501     AssimpImporter();
502 
503     bool load(const QString &filename) override;
504 
505 private:
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 
AssimpImporter()520 AssimpImporter::AssimpImporter() :
521     m_importer(new Assimp::Importer)
522 {
523     m_importer->SetIOHandler(new AssimpIOSystem);
524     m_importer->SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT);
525 }
526 
load(const QString & filename)527 bool 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(filename.toUtf8().constData(), 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(scene->mRootNode);
553     }
554 
555     buildBuffer();
556     parseEmbeddedTextures();
557     parseMaterials();
558     parseCameras();
559     parseScene();
560     parseAnimations();
561 
562     return true;
563 }
564 
printNodes(const aiNode * node,int level)565 void AssimpImporter::printNodes(const aiNode *node, int level)
566 {
567     qDebug().noquote() << QString().fill('-', level * 4) << ai2qt(node->mName) << node->mNumMeshes << "mesh refs";
568     for (uint i = 0; i < node->mNumChildren; ++i)
569         printNodes(node->mChildren[i], level + 1);
570 }
571 
copyIndexBuf(T * dst,const aiMesh * src)572 template<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("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 
newBufferViewName()584 static QString newBufferViewName()
585 {
586     static int cnt = 0;
587     return QString(QStringLiteral("bufferView_%1")).arg(++cnt);
588 }
589 
newAccessorName()590 static QString newAccessorName()
591 {
592     static int cnt = 0;
593     return QString(QStringLiteral("accessor_%1")).arg(++cnt);
594 }
595 
newMeshName()596 static QString newMeshName()
597 {
598     static int cnt = 0;
599     return QString(QStringLiteral("mesh_%1")).arg(++cnt);
600 }
601 
newMaterialName()602 static QString newMaterialName()
603 {
604     static int cnt = 0;
605     return QString(QStringLiteral("material_%1")).arg(++cnt);
606 }
607 
newTechniqueName()608 static QString newTechniqueName()
609 {
610     static int cnt = 0;
611     return QString(QStringLiteral("technique_%1")).arg(++cnt);
612 }
613 
newTextureName()614 static QString newTextureName()
615 {
616     static int cnt = 0;
617     return QString(QStringLiteral("texture_%1")).arg(++cnt);
618 }
619 
newImageName()620 static QString newImageName()
621 {
622     static int cnt = 0;
623     return QString(QStringLiteral("image_%1")).arg(++cnt);
624 }
625 
newShaderName()626 static QString newShaderName()
627 {
628     static int cnt = 0;
629     return QString(QStringLiteral("shader_%1")).arg(++cnt);
630 }
631 
newProgramName()632 static QString newProgramName()
633 {
634     static int cnt = 0;
635     return QString(QStringLiteral("program_%1")).arg(++cnt);
636 }
637 
newNodeName()638 static QString newNodeName()
639 {
640     static int cnt = 0;
641     return QString(QStringLiteral("node_%1")).arg(++cnt);
642 }
643 
newAnimationName()644 static QString newAnimationName()
645 {
646     static int cnt = 0;
647     return QString(QStringLiteral("animation_%1")).arg(++cnt);
648 }
649 
calcBB(QVector<float> & minVal,QVector<float> & maxVal,T * data,int vertexCount,int compCount)650 template<class T> void calcBB(QVector<float> &minVal, QVector<float> &maxVal, T *data, int vertexCount, int compCount)
651 {
652     minVal.resize(compCount);
653     maxVal.resize(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 
buildBuffer()671 void 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(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(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(vertexBufView);
790 
791         QByteArray indexBuf;
792         uint indexCount = m->mNumFaces * 3;
793         if (indexCount >= USHRT_MAX) {
794             indexBuf.resize(indexCount * sizeof(quint32));
795             quint32 *p = reinterpret_cast<quint32 *>(indexBuf.data());
796             copyIndexBuf(p, m);
797         } else {
798             indexBuf.resize(indexCount * sizeof(quint16));
799             quint16 *p = reinterpret_cast<quint16 *>(indexBuf.data());
800             copyIndexBuf(p, 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(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(acc.minVal, acc.maxVal, vertices, m->mNumVertices, 3);
823         meshInfo.accessors.append(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(acc.minVal, acc.maxVal, normals, m->mNumVertices, 3);
832         meshInfo.accessors.append(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(acc.minVal, acc.maxVal, textureCoords, m->mNumVertices, 2);
843             meshInfo.accessors.append(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(acc.minVal, acc.maxVal, colors, m->mNumVertices, 4);
855             meshInfo.accessors.append(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(acc.minVal, acc.maxVal, tangents, m->mNumVertices, 3);
867             meshInfo.accessors.append(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(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(meshInfo.views)) sl << bv.name;
894             qDebug() << "  buffer views:" << sl;
895             sl.clear();
896             for (const MeshInfo::Accessor &acc : qAsConst(meshInfo.accessors)) sl << acc.name;
897             qDebug() << "  accessors:" << sl;
898             qDebug() << "  material: #" << meshInfo.materialIndex;
899         }
900 
901         m_buffer.append(vertexBuf);
902         m_buffer.append(indexBuf);
903 
904         m_meshInfo.insert(i, meshInfo);
905     }
906 
907     if (opts.showLog)
908         qDebug().noquote() << "Total buffer size" << m_buffer.size();
909 }
910 
parseEmbeddedTextures()911 void 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 
parseMaterials()953 void 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, s) == aiReturn_SUCCESS)
969             matInfo.originalName = ai2qt(s);
970 
971         aiColor4D color;
972         if (mat->Get(AI_MATKEY_COLOR_DIFFUSE, color) == aiReturn_SUCCESS)
973             matInfo.m_colors.insert("diffuse", QVector<float>() << color.r << color.g << color.b << color.a);
974         if (mat->Get(AI_MATKEY_COLOR_SPECULAR, color) == aiReturn_SUCCESS)
975             matInfo.m_colors.insert("specular", QVector<float>() << color.r << color.g << color.b);
976         if (mat->Get(AI_MATKEY_COLOR_AMBIENT, color) == aiReturn_SUCCESS)
977             matInfo.m_colors.insert("ambient", QVector<float>() << color.r << color.g << color.b);
978 
979         float f;
980         if (mat->Get(AI_MATKEY_SHININESS, f) == aiReturn_SUCCESS)
981             matInfo.m_values.insert("shininess", f);
982 
983         if (mat->GetTexture(aiTextureType_DIFFUSE, 0, &s) == aiReturn_SUCCESS)
984             matInfo.m_textures.insert("diffuse", ai2qt(s));
985         if (mat->GetTexture(aiTextureType_SPECULAR, 0, &s) == aiReturn_SUCCESS)
986             matInfo.m_textures.insert("specular", ai2qt(s));
987         if (mat->GetTexture(aiTextureType_NORMALS, 0, &s) == aiReturn_SUCCESS)
988             matInfo.m_textures.insert("normal", ai2qt(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('*'))
994                 *texIt = m_embeddedTextures[*texIt].name;
995             else
996                 m_externalTextures.insert(*texIt);
997             ++texIt;
998         }
999 
1000         m_materialInfo.insert(i, 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 
parseCameras()1011 void 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(cam->mName);
1022         CameraInfo c;
1023 
1024         c.name = name + QStringLiteral("_cam");
1025         c.aspectRatio = qFuzzyIsNull(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(name, c);
1036 
1037         if (opts.showLog)
1038             qDebug().noquote() << "#" << i << "(" << name << ")" << c.aspectRatio << c.yfov << c.znear << c.zfar;
1039     }
1040 }
1041 
parseNode(Importer::Node * dst,const aiNode * src)1042 void AssimpImporter::parseNode(Importer::Node *dst, const aiNode *src)
1043 {
1044     dst->name = ai2qt(src->mName);
1045     dst->uniqueName = newNodeName();
1046     for (uint j = 0; j < src->mNumChildren; ++j) {
1047         Node *c = new Node;
1048         parseNode(c, src->mChildren[j]);
1049         dst->children << c;
1050     }
1051     dst->transformation = ai2qt(src->mTransformation);
1052     for (uint j = 0; j < src->mNumMeshes; ++j)
1053         dst->meshes << src->mMeshes[j];
1054 }
1055 
parseScene()1056 void AssimpImporter::parseScene()
1057 {
1058     delNode(m_rootNode);
1059     const aiScene *sc = scene();
1060     m_rootNode = new Node;
1061     parseNode(m_rootNode, sc->mRootNode);
1062 }
1063 
addKeyFrame(QVector<KeyFrame> & keyFrames,float t,aiVector3D * vt,aiQuaternion * vr,aiVector3D * vs)1064 void 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(keyFrames[i].t, 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(kf);
1094 }
1095 
parseAnimations()1096 void 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(anim->mName) << "->" << ai2qt(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, &v, nullptr, 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, nullptr, &v, 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, nullptr, nullptr, &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(anim->mName);
1140                 QString nodeName = ai2qt(a->mNodeName); // have to map to our generated, unique node names
1141                 const Node *targetNode = findNode(m_rootNode, 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(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 
scene() const1168 const aiScene *AssimpImporter::scene() const
1169 {
1170     return m_importer->GetScene();
1171 }
1172 
1173 class Exporter
1174 {
1175 public:
Exporter(Importer * importer)1176     Exporter(Importer *importer) : m_importer(importer) { }
~Exporter()1177     virtual ~Exporter() { }
1178 
1179     virtual void save(const QString &inputFilename) = 0;
1180 
1181 protected:
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 
nodeIsUseful(const Importer::Node * n) const1192 bool Exporter::nodeIsUseful(const Importer::Node *n) const
1193 {
1194     if (!n->meshes.isEmpty() || m_importer->cameraInfo().contains(n->name))
1195         return true;
1196 
1197     for (const Importer::Node *c : n->children) {
1198         if (nodeIsUseful(c))
1199             return true;
1200     }
1201 
1202     return false;
1203 }
1204 
copyExternalTextures(const QString & inputFilename)1205 void 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(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(dst);
1218             }
1219         }
1220     }
1221 }
1222 
exportEmbeddedTextures()1223 void 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 
compressTextures()1237 void 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(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(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(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(src, dst);
1264             m_files.remove(src);
1265             m_files.insert(dst);
1266         }
1267     }
1268 }
1269 
1270 class GltfExporter : public Exporter
1271 {
1272 public:
1273     GltfExporter(Importer *importer);
1274     void save(const QString &inputFilename) override;
1275 
1276 private:
1277     struct ProgramInfo {
1278         struct Param {
ParamGltfExporter::ProgramInfo::Param1279             Param() : type(0) { }
ParamGltfExporter::ProgramInfo::Param1280             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 {
TechniqueInfoGltfExporter::TechniqueInfo1312         TechniqueInfo() : opaque(true), prog(nullptr) { }
TechniqueInfoGltfExporter::TechniqueInfo1313         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 };
1336 QT_BEGIN_NAMESPACE
1337 Q_DECLARE_TYPEINFO(GltfExporter::ProgramInfo,        Q_MOVABLE_TYPE);
1338 Q_DECLARE_TYPEINFO(GltfExporter::ProgramInfo::Param, Q_MOVABLE_TYPE);
1339 Q_DECLARE_TYPEINFO(GltfExporter::TechniqueInfo,      Q_MOVABLE_TYPE);
1340 QT_END_NAMESPACE
1341 
GltfExporter(Importer * importer)1342 GltfExporter::GltfExporter(Importer *importer)
1343     : Exporter(importer)
1344 {
1345     initShaderInfo();
1346 }
1347 
1348 struct Shader {
1349     const char *name;
1350     const char *text;
1351 } shaders[] = {
1352     {
1353         "color.vert",
1354 "$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         "color.frag",
1371 "$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         "diffusemap.vert",
1399 "$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         "diffusemap.frag",
1419 "$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         "diffusespecularmap.frag",
1449 "$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         "normaldiffusemap.vert",
1480 "$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         "normaldiffusemap.frag",
1512 "$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         "normaldiffusespecularmap.frag",
1544 "$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 
initShaderInfo()1577 void 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"), 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"), 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 "), QByteArray());
1691 }
1692 
chooseProgram(uint materialIndex)1693 GltfExporter::ProgramInfo *GltfExporter::chooseProgram(uint materialIndex)
1694 {
1695     Importer::MaterialInfo matInfo = m_importer->materialInfo(materialIndex);
1696     const bool hasNormalTexture = matInfo.m_textures.contains("normal");
1697     const bool hasSpecularTexture = matInfo.m_textures.contains("specular");
1698     const bool hasDiffuseTexture = matInfo.m_textures.contains("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 
exportNode(const Importer::Node * n,QJsonObject & nodes)1732 QString 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(c))
1739             children << exportNode(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(*mtxp++);
1746     node["matrix"] = matrix;
1747     QJsonArray meshList;
1748     for (int j = 0; j < n->meshes.count(); ++j)
1749         meshList.append(m_importer->meshInfo(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(n->name))
1755             node["camera"] = cam[n->name].name;
1756     }
1757 
1758     nodes[n->uniqueName] = node;
1759     return n->uniqueName;
1760 }
1761 
col2jsvec(const QVector<float> & color,bool alpha=false)1762 static 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 
vec2jsvec(const QVector<float> & v)1771 static 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 
promoteColorsToRGBA(QJsonObject * obj)1779 static 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(1);
1790                 *it = arr;
1791             }
1792         }
1793         ++it;
1794     }
1795 }
1796 
exportMaterials(QJsonObject & materials,QHash<QString,QString> * textureNameMap)1797 void 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(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(it.value()))
1808                 textureNameMap->insert(it.value(), 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(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(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(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(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(it.value(), alpha);
1854         }
1855         if (opts.shaders)
1856             material["values"] = vals;
1857 
1858         ProgramInfo *prog = chooseProgram(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(techniqueInfo);
1873             m_usedPrograms.insert(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(&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 
writeShader(const QString & src,const QString & dst,const QVector<QPair<QByteArray,QByteArray>> & substTab)1913 void 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(shader.name, name.constData())) {
1918             QString outfn = opts.outDir + dst;
1919             QFile outf(outfn);
1920             if (outf.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
1921                 m_files.insert(QFileInfo(outf.fileName()).fileName());
1922                 if (opts.showLog)
1923                     qDebug() << "Writing" << outfn;
1924                 const auto lines = QString::fromUtf8(shader.text).split('\n');
1925                 for (QString line : lines) {
1926                     for (const auto &subst : substTab)
1927                         line.replace(subst.first, subst.second);
1928                     line += QStringLiteral("\n");
1929                     outf.write(line.toUtf8());
1930                 }
1931             }
1932             return;
1933         }
1934     }
1935     qWarning() << "ERROR: No shader found for" << src;
1936 }
1937 
exportParameter(QJsonObject & dst,const QVector<ProgramInfo::Param> & params)1938 void 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 
1953 namespace {
1954 struct ProgramNames
1955 {
1956     QString name;
1957     QString coreName;
1958 };
1959 }
1960 
exportTechniques(QJsonObject & obj,const QString & basename)1961 void 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(m_usedPrograms)) {
1969         QString newName;
1970         if (!shaderMap.contains(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(key);
1977             vertexShader["uri"] = fn;
1978             writeShader(prog->vertShader, fn, m_subst_es2);
1979             if (opts.genCore) {
1980                 QJsonObject coreVertexShader;
1981                 QString coreKey = QString(QStringLiteral("%1_core").arg(key));
1982                 fn = QString(QStringLiteral("%1.vert")).arg(coreKey);
1983                 coreVertexShader["type"] = 35633;
1984                 coreVertexShader["uri"] = fn;
1985                 writeShader(prog->vertShader, fn, m_subst_core);
1986                 shaders[coreKey] = coreVertexShader;
1987                 shaderMap.insert(QString(prog->vertShader + QStringLiteral("_core")), coreKey);
1988             }
1989             shaders[key] = vertexShader;
1990             shaderMap.insert(prog->vertShader, key);
1991         }
1992         if (!shaderMap.contains(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(key);
1999             fragmentShader["uri"] = fn;
2000             writeShader(prog->fragShader, fn, m_subst_es2);
2001             if (opts.genCore) {
2002                 QJsonObject coreFragmentShader;
2003                 QString coreKey = QString(QStringLiteral("%1_core").arg(key));
2004                 fn = QString(QStringLiteral("%1.frag")).arg(coreKey);
2005                 coreFragmentShader["type"] = 35632;
2006                 coreFragmentShader["uri"] = fn;
2007                 writeShader(prog->fragShader, fn, m_subst_core);
2008                 shaders[coreKey] = coreFragmentShader;
2009                 shaderMap.insert(QString(prog->fragShader + QStringLiteral("_core")), coreKey);
2010             }
2011             shaders[key] = fragmentShader;
2012             shaderMap.insert(prog->fragShader, key);
2013         }
2014     }
2015     obj["shaders"] = shaders;
2016 
2017     QJsonObject programs;
2018     QHash<const ProgramInfo *, ProgramNames> programMap;
2019     for (const ProgramInfo *prog : qAsConst(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(m_techniques)) {
2047         QJsonObject technique;
2048         QJsonObject parameters;
2049         const ProgramInfo *prog = techniqueInfo.prog;
2050         exportParameter(parameters, prog->attributes);
2051         exportParameter(parameters, 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 
exportAnimations(QJsonObject & obj,QVector<Importer::BufferInfo> & bufList,QVector<Importer::MeshInfo::BufferView> & bvList,QVector<Importer::MeshInfo::Accessor> & accList)2090 void 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(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(0);
2193                 *p++ = v->at(1);
2194                 *p++ = v->at(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(1); // x
2210                 *p++ = v->at(2); // y
2211                 *p++ = v->at(3); // z
2212                 *p++ = v->at(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(0);
2228                 *p++ = v->at(1);
2229                 *p++ = v->at(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(bv.length);
2258         buf.data += extraData;
2259         if (opts.showLog)
2260             qDebug().noquote() << "Animation data in buffer uses" << extraData.size() << "bytes";
2261     }
2262 }
2263 
save(const QString & inputFilename)2264 void 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(m_obj, bufList, bvList, accList);
2289 
2290     QJsonObject asset;
2291     asset["generator"] = QString(QStringLiteral("qgltf %1")).arg(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(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(QIODevice::WriteOnly | QIODevice::Truncate)) {
2302             m_files.insert(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(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(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(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(acc.minVal);
2346             accessor["max"] = vec2jsvec(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(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(meshInfo.materialIndex).name;
2369         prims.append(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(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);
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(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(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(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(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);
2463 QT_WARNING_PUSH
2464 QT_WARNING_DISABLE_DEPRECATED
2465     const QByteArray json = opts.genBin
2466             ? m_doc.toBinaryData()
2467             : m_doc.toJson(opts.compact ? QJsonDocument::Compact : QJsonDocument::Indented);
2468 QT_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(openMode)) {
2480         m_files.insert(QFileInfo(f.fileName()).fileName());
2481         f.write(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(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
2490         QByteArray pre = "<RCC><qresource prefix=\"/models\">\n";
2491         QByteArray post = "</qresource></RCC>\n";
2492         f.write(pre);
2493         for (const QString &file : qAsConst(m_files)) {
2494             QString line = QString(QStringLiteral("  <file>%1</file>\n")).arg(file);
2495             f.write(line.toUtf8());
2496         }
2497         f.write(post);
2498         f.close();
2499     }
2500 
2501     if (opts.showLog)
2502         qDebug() << "Done\n";
2503 }
2504 
2505 static 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 
main(int argc,char ** argv)2513 int 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(description));
2523     QCommandLineOption outDirOpt(QStringLiteral("d"), QStringLiteral("Place all output data into <dir>"), QStringLiteral("dir"));
2524     cmdLine.addOption(outDirOpt);
2525 #ifndef QT_BOOTSTRAPPED
2526     QCommandLineOption binOpt(QStringLiteral("b"), QStringLiteral("Store binary JSON data in the .qgltf file"));
2527     cmdLine.addOption(binOpt);
2528 #endif
2529     QCommandLineOption compactOpt(QStringLiteral("m"), QStringLiteral("Store compact JSON in the .qgltf file"));
2530     cmdLine.addOption(compactOpt);
2531     QCommandLineOption compOpt(QStringLiteral("c"), QStringLiteral("qCompress() vertex/index data in the .bin file"));
2532     cmdLine.addOption(compOpt);
2533     QCommandLineOption tangentOpt(QStringLiteral("t"), QStringLiteral("Generate tangent vectors"));
2534     cmdLine.addOption(tangentOpt);
2535     QCommandLineOption nonInterleavedOpt(QStringLiteral("n"), QStringLiteral("Use non-interleaved buffer layout"));
2536     cmdLine.addOption(nonInterleavedOpt);
2537     QCommandLineOption scaleOpt(QStringLiteral("e"), QStringLiteral("Scale vertices by the float scale factor <factor>"), QStringLiteral("factor"));
2538     cmdLine.addOption(scaleOpt);
2539     QCommandLineOption coreOpt(QStringLiteral("g"), QStringLiteral("Generate OpenGL 3.2+ core profile shaders too"));
2540     cmdLine.addOption(coreOpt);
2541     QCommandLineOption etc1Opt(QStringLiteral("1"), QStringLiteral("Generate ETC1 compressed textures by invoking etc1tool (PNG only)"));
2542     cmdLine.addOption(etc1Opt);
2543     QCommandLineOption noCommonMatOpt(QStringLiteral("T"), QStringLiteral("Do not generate KHR_materials_common block"));
2544     cmdLine.addOption(noCommonMatOpt);
2545     QCommandLineOption noShadersOpt(QStringLiteral("S"), QStringLiteral("Do not generate shaders/programs/techniques"));
2546     cmdLine.addOption(noShadersOpt);
2547     QCommandLineOption silentOpt(QStringLiteral("s"), QStringLiteral("Silence debug output"));
2548     cmdLine.addOption(silentOpt);
2549     cmdLine.process(app);
2550     opts.outDir = cmdLine.value(outDirOpt);
2551 #ifndef QT_BOOTSTRAPPED
2552     opts.genBin = cmdLine.isSet(binOpt);
2553 #endif
2554     opts.compact = cmdLine.isSet(compactOpt);
2555     opts.compress = cmdLine.isSet(compOpt);
2556     opts.genTangents = cmdLine.isSet(tangentOpt);
2557     opts.interleave = !cmdLine.isSet(nonInterleavedOpt);
2558     opts.scale = 1;
2559     if (cmdLine.isSet(scaleOpt)) {
2560         bool ok = false;
2561         float v;
2562         v = cmdLine.value(scaleOpt).toFloat(&ok);
2563         if (ok)
2564             opts.scale = v;
2565     }
2566     opts.genCore = cmdLine.isSet(coreOpt);
2567     opts.texComp = cmdLine.isSet(etc1Opt) ? Options::ETC1 : Options::NoTextureCompression;
2568     opts.commonMat = !cmdLine.isSet(noCommonMatOpt);
2569     opts.shaders = !cmdLine.isSet(noShadersOpt);
2570     opts.showLog = !cmdLine.isSet(silentOpt);
2571     if (!opts.outDir.isEmpty()) {
2572         if (!opts.outDir.endsWith('/'))
2573             opts.outDir.append('/');
2574         QDir().mkpath(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(fn)) {
2585             qWarning() << "Failed to import" << fn;
2586             continue;
2587         }
2588         exporter.save(fn);
2589     }
2590 
2591     return 0;
2592 }
2593