summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@theqtcompany.com>2015-08-17 13:50:47 +0200
committerLaszlo Agocs <laszlo.agocs@theqtcompany.com>2015-08-18 11:30:30 +0000
commitcc17811995b382280a65a30fdd93bc92c5c01478 (patch)
treef4e041b409020f0b4536517c1c9eeaffa2e51d1c /tools
parentff6eb8b14dd6754c1db6b444d7012601ad6e45ce (diff)
Rename tools directory back
And remove force_bootstrap that breaks non-cross-compiled builds. In true cross-compiled builds cross_compile (that is set during configure) will trigger force_bootstrap so we don't have to set that manually for tools outside qtbase. Change-Id: Ie3bc6fc105ea7686a3098c6afd631561755aa01b Reviewed-by: Andy Nichols <andy.nichols@theqtcompany.com>
Diffstat (limited to 'tools')
-rw-r--r--tools/qgltf/qgltf.cpp2509
-rw-r--r--tools/qgltf/qgltf.prf31
-rw-r--r--tools/qgltf/qgltf.pro11
-rw-r--r--tools/tools.pro3
4 files changed, 2554 insertions, 0 deletions
diff --git a/tools/qgltf/qgltf.cpp b/tools/qgltf/qgltf.cpp
new file mode 100644
index 000000000..a62ce7d6b
--- /dev/null
+++ b/tools/qgltf/qgltf.cpp
@@ -0,0 +1,2509 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd.
+** Contact: http://www.qt.io/licensing/
+**
+** This file is part of the Qt3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <assimp/Importer.hpp>
+#include <assimp/IOStream.hpp>
+#include <assimp/IOSystem.hpp>
+#include <assimp/scene.h>
+#include <assimp/postprocess.h>
+
+#include <qiodevice.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qdir.h>
+#include <qhash.h>
+#include <qdebug.h>
+#include <qcoreapplication.h>
+#include <qcommandlineparser.h>
+#include <qjsondocument.h>
+#include <qjsonobject.h>
+#include <qjsonarray.h>
+#include <qmath.h>
+
+#define GLT_UNSIGNED_SHORT 0x1403
+#define GLT_UNSIGNED_INT 0x1405
+#define GLT_FLOAT 0x1406
+
+#define GLT_FLOAT_VEC2 0x8B50
+#define GLT_FLOAT_VEC3 0x8B51
+#define GLT_FLOAT_VEC4 0x8B52
+#define GLT_FLOAT_MAT3 0x8B5B
+#define GLT_FLOAT_MAT4 0x8B5C
+#define GLT_SAMPLER_2D 0x8B5E
+
+#define GLT_ARRAY_BUFFER 0x8892
+#define GLT_ELEMENT_ARRAY_BUFFER 0x8893
+
+#define GLT_DEPTH_TEST 0x0B71
+#define GLT_CULL_FACE 0x0B44
+#define GLT_BLEND 0x0BE2
+
+class AssimpIOStream : public Assimp::IOStream
+{
+public:
+ AssimpIOStream(QIODevice *device);
+ ~AssimpIOStream();
+
+ size_t Read(void *pvBuffer, size_t pSize, size_t pCount) Q_DECL_OVERRIDE;
+ size_t Write(const void *pvBuffer, size_t pSize, size_t pCount) Q_DECL_OVERRIDE;
+ aiReturn Seek(size_t pOffset, aiOrigin pOrigin) Q_DECL_OVERRIDE;
+ size_t Tell() const Q_DECL_OVERRIDE;
+ size_t FileSize() const ;
+ void Flush() Q_DECL_OVERRIDE;
+
+private:
+ QIODevice *m_device;
+};
+
+class AssimpIOSystem : public Assimp::IOSystem
+{
+public:
+ AssimpIOSystem();
+ bool Exists(const char *pFile) const Q_DECL_OVERRIDE;
+ char getOsSeparator() const Q_DECL_OVERRIDE;
+ Assimp::IOStream *Open(const char *pFile, const char *pMode) Q_DECL_OVERRIDE;
+ void Close(Assimp::IOStream *pFile) Q_DECL_OVERRIDE;
+
+private:
+ QHash<QByteArray, QIODevice::OpenMode> m_openModeMap;
+};
+
+AssimpIOStream::AssimpIOStream(QIODevice *device) :
+ m_device(device)
+{
+ Q_ASSERT(m_device);
+}
+
+AssimpIOStream::~AssimpIOStream()
+{
+ delete m_device;
+}
+
+size_t AssimpIOStream::Read(void *pvBuffer, size_t pSize, size_t pCount)
+{
+ qint64 readBytes = m_device->read((char *)pvBuffer, pSize * pCount);
+ if (readBytes < 0)
+ qWarning() << Q_FUNC_INFO << " read failed";
+ return readBytes;
+}
+
+size_t AssimpIOStream::Write(const void *pvBuffer, size_t pSize, size_t pCount)
+{
+ qint64 writtenBytes = m_device->write((char *)pvBuffer, pSize * pCount);
+ if (writtenBytes < 0)
+ qWarning() << Q_FUNC_INFO << " write failed";
+ return writtenBytes;
+}
+
+aiReturn AssimpIOStream::Seek(size_t pOffset, aiOrigin pOrigin)
+{
+ qint64 seekPos = pOffset;
+
+ if (pOrigin == aiOrigin_CUR)
+ seekPos += m_device->pos();
+ else if (pOrigin == aiOrigin_END)
+ seekPos += m_device->size();
+
+ if (!m_device->seek(seekPos)) {
+ qWarning() << Q_FUNC_INFO << " seek failed";
+ return aiReturn_FAILURE;
+ }
+ return aiReturn_SUCCESS;
+}
+
+size_t AssimpIOStream::Tell() const
+{
+ return m_device->pos();
+}
+
+size_t AssimpIOStream::FileSize() const
+{
+ return m_device->size();
+}
+
+void AssimpIOStream::Flush()
+{
+ // we don't write via assimp
+}
+
+AssimpIOSystem::AssimpIOSystem()
+{
+ m_openModeMap[QByteArrayLiteral("r")] = QIODevice::ReadOnly;
+ m_openModeMap[QByteArrayLiteral("r+")] = QIODevice::ReadWrite;
+ m_openModeMap[QByteArrayLiteral("w")] = QIODevice::WriteOnly | QIODevice::Truncate;
+ m_openModeMap[QByteArrayLiteral("w+")] = QIODevice::ReadWrite | QIODevice::Truncate;
+ m_openModeMap[QByteArrayLiteral("a")] = QIODevice::WriteOnly | QIODevice::Append;
+ m_openModeMap[QByteArrayLiteral("a+")] = QIODevice::ReadWrite | QIODevice::Append;
+ m_openModeMap[QByteArrayLiteral("wb")] = QIODevice::WriteOnly;
+ m_openModeMap[QByteArrayLiteral("wt")] = QIODevice::WriteOnly | QIODevice::Text;
+ m_openModeMap[QByteArrayLiteral("rb")] = QIODevice::ReadOnly;
+ m_openModeMap[QByteArrayLiteral("rt")] = QIODevice::ReadOnly | QIODevice::Text;
+}
+
+bool AssimpIOSystem::Exists(const char *pFile) const
+{
+ return QFileInfo(QString::fromUtf8(pFile)).exists();
+}
+
+char AssimpIOSystem::getOsSeparator() const
+{
+ return QDir::separator().toLatin1();
+}
+
+Assimp::IOStream *AssimpIOSystem::Open(const char *pFile, const char *pMode)
+{
+ const QString fileName(QString::fromUtf8(pFile));
+ const QByteArray cleanedMode(QByteArray(pMode).trimmed());
+
+ const QIODevice::OpenMode openMode = m_openModeMap.value(cleanedMode, QIODevice::NotOpen);
+
+ QScopedPointer<QFile> file(new QFile(fileName));
+ if (file->open(openMode))
+ return new AssimpIOStream(file.take());
+
+ return Q_NULLPTR;
+}
+
+void AssimpIOSystem::Close(Assimp::IOStream *pFile)
+{
+ delete pFile;
+}
+
+static inline QString ai2qt(const aiString &str)
+{
+ return QString::fromUtf8(str.data, int(str.length));
+}
+
+static inline QVector<float> ai2qt(const aiMatrix4x4 &matrix)
+{
+ return QVector<float>() << matrix.a1 << matrix.b1 << matrix.c1 << matrix.d1
+ << matrix.a2 << matrix.b2 << matrix.c2 << matrix.d2
+ << matrix.a3 << matrix.b3 << matrix.c3 << matrix.d3
+ << matrix.a4 << matrix.b4 << matrix.c4 << matrix.d4;
+}
+
+struct Options {
+ QString outDir;
+ bool genBin;
+ bool compact;
+ bool compress;
+ bool genTangents;
+ bool interleave;
+ float scale;
+ bool genCore;
+ enum TextureCompression {
+ NoTextureCompression,
+ ETC1
+ };
+ TextureCompression texComp;
+ bool showLog;
+} opts;
+
+class Importer
+{
+public:
+ Importer();
+ virtual ~Importer();
+
+ virtual bool load(const QString &filename) = 0;
+
+ struct BufferInfo {
+ QString name;
+ QByteArray data;
+ };
+ QVector<BufferInfo> buffers() const;
+
+ struct MeshInfo {
+ struct BufferView {
+ BufferView() : bufIndex(0), offset(0), length(0), componentType(0), target(0) { }
+ QString name;
+ uint bufIndex;
+ uint offset;
+ uint length;
+ uint componentType;
+ uint target;
+ };
+ QVector<BufferView> views;
+ struct Accessor {
+ Accessor() : offset(0), stride(0), count(0), componentType(0) { }
+ QString name;
+ QString usage;
+ QString bufferView;
+ uint offset;
+ uint stride;
+ uint count;
+ uint componentType;
+ QString type;
+ QVector<float> minVal;
+ QVector<float> maxVal;
+ };
+ QVector<Accessor> accessors;
+ QString name; // generated
+ QString originalName; // may be empty
+ uint materialIndex;
+ };
+
+ QVector<MeshInfo::BufferView> bufferViews() const;
+ QVector<MeshInfo::Accessor> accessors() const;
+ uint meshCount() const;
+ MeshInfo meshInfo(uint meshIndex) const;
+
+ struct MaterialInfo {
+ QString name;
+ QString originalName;
+ QHash<QByteArray, QVector<float> > m_colors;
+ QHash<QByteArray, float> m_values;
+ QHash<QByteArray, QString> m_textures;
+ };
+ uint materialCount() const;
+ MaterialInfo materialInfo(uint materialIndex) const;
+
+ QSet<QString> externalTextures() const;
+
+ struct CameraInfo {
+ QString name; // suffixed
+ float aspectRatio;
+ float yfov;
+ float zfar;
+ float znear;
+ };
+ QHash<QString, CameraInfo> cameraInfo() const;
+
+ struct EmbeddedTextureInfo {
+ EmbeddedTextureInfo() { }
+ QString name;
+#ifdef HAS_QIMAGE
+ EmbeddedTextureInfo(const QString &name, const QImage &image) : name(name), image(image) { }
+ QImage image;
+#endif
+ };
+ QHash<QString, EmbeddedTextureInfo> embeddedTextures() const;
+
+ struct Node {
+ QString name;
+ QString uniqueName; // generated
+ QVector<float> transformation;
+ QVector<Node *> children;
+ QVector<uint> meshes;
+ };
+ const Node *rootNode() const;
+
+ struct KeyFrame {
+ KeyFrame() : t(0), transValid(false), rotValid(false), scaleValid(false) { }
+ float t;
+ bool transValid;
+ QVector<float> trans;
+ bool rotValid;
+ QVector<float> rot;
+ bool scaleValid;
+ QVector<float> scale;
+ };
+ struct AnimationInfo {
+ AnimationInfo() : hasTranslation(false), hasRotation(false), hasScale(false) { }
+ QString name;
+ QString targetNode;
+ bool hasTranslation;
+ bool hasRotation;
+ bool hasScale;
+ QVector<KeyFrame> keyFrames;
+ };
+ QVector<AnimationInfo> animations() const;
+
+ bool allMeshesForMaterialHaveTangents(uint materialIndex) const;
+
+ const Node *findNode(const Node *root, const QString &originalName) const;
+
+protected:
+ void delNode(Importer::Node *n);
+
+ QByteArray m_buffer;
+ QHash<uint, MeshInfo> m_meshInfo;
+ QHash<uint, MaterialInfo> m_materialInfo;
+ QHash<QString, EmbeddedTextureInfo> m_embeddedTextures;
+ QSet<QString> m_externalTextures;
+ QHash<QString, CameraInfo> m_cameraInfo;
+ Node *m_rootNode;
+ QVector<AnimationInfo> m_animations;
+};
+
+Importer::Importer()
+ : m_rootNode(Q_NULLPTR)
+{
+}
+
+void Importer::delNode(Importer::Node *n)
+{
+ if (!n)
+ return;
+ foreach (Importer::Node *c, n->children)
+ delNode(c);
+ delete n;
+}
+
+Importer::~Importer()
+{
+ delNode(m_rootNode);
+}
+
+QVector<Importer::BufferInfo> Importer::buffers() const
+{
+ BufferInfo b;
+ b.name = QStringLiteral("buf");
+ b.data = m_buffer;
+ return QVector<BufferInfo>() << b;
+}
+
+const Importer::Node *Importer::rootNode() const
+{
+ return m_rootNode;
+}
+
+bool Importer::allMeshesForMaterialHaveTangents(uint materialIndex) const
+{
+ foreach (const MeshInfo &mi, m_meshInfo) {
+ if (mi.materialIndex == materialIndex) {
+ bool hasTangents = false;
+ foreach (const MeshInfo::Accessor &acc, mi.accessors) {
+ if (acc.usage == QStringLiteral("TANGENT")) {
+ hasTangents = true;
+ break;
+ }
+ }
+ if (!hasTangents)
+ return false;
+ }
+ }
+ return true;
+}
+
+QVector<Importer::MeshInfo::BufferView> Importer::bufferViews() const
+{
+ QVector<Importer::MeshInfo::BufferView> bv;
+ foreach (const MeshInfo &mi, m_meshInfo) {
+ foreach (const MeshInfo::BufferView &v, mi.views)
+ bv << v;
+ }
+ return bv;
+}
+
+QVector<Importer::MeshInfo::Accessor> Importer::accessors() const
+{
+ QVector<Importer::MeshInfo::Accessor> acc;
+ foreach (const MeshInfo &mi, m_meshInfo) {
+ foreach (const MeshInfo::Accessor &a, mi.accessors)
+ acc << a;
+ }
+ return acc;
+}
+
+uint Importer::meshCount() const
+{
+ return m_meshInfo.count();
+}
+
+Importer::MeshInfo Importer::meshInfo(uint meshIndex) const
+{
+ return m_meshInfo[meshIndex];
+}
+
+uint Importer::materialCount() const
+{
+ return m_materialInfo.count();
+}
+
+Importer::MaterialInfo Importer::materialInfo(uint materialIndex) const
+{
+ return m_materialInfo[materialIndex];
+}
+
+QHash<QString, Importer::CameraInfo> Importer::cameraInfo() const
+{
+ return m_cameraInfo;
+}
+
+QSet<QString> Importer::externalTextures() const
+{
+ return m_externalTextures;
+}
+
+QHash<QString, Importer::EmbeddedTextureInfo> Importer::embeddedTextures() const
+{
+ return m_embeddedTextures;
+}
+
+QVector<Importer::AnimationInfo> Importer::animations() const
+{
+ return m_animations;
+}
+
+const Importer::Node *Importer::findNode(const Node *root, const QString &originalName) const
+{
+ foreach (const Node *c, root->children) {
+ if (c->name == originalName)
+ return c;
+ const Node *cn = findNode(c, originalName);
+ if (cn)
+ return cn;
+ }
+ return Q_NULLPTR;
+}
+
+class AssimpImporter : public Importer
+{
+public:
+ AssimpImporter();
+
+ bool load(const QString &filename) Q_DECL_OVERRIDE;
+
+private:
+ const aiScene *scene() const;
+ void printNodes(const aiNode *node, int level = 1);
+ void buildBuffer();
+ void parseEmbeddedTextures();
+ void parseMaterials();
+ void parseCameras();
+ void parseNode(Importer::Node *dst, const aiNode *src);
+ void parseScene();
+ void parseAnimations();
+ void addKeyFrame(QVector<KeyFrame> &keyFrames, float t, aiVector3D *vt, aiQuaternion *vr, aiVector3D *vs);
+
+ QScopedPointer<Assimp::Importer> m_importer;
+};
+
+AssimpImporter::AssimpImporter() :
+ m_importer(new Assimp::Importer)
+{
+ m_importer->SetIOHandler(new AssimpIOSystem);
+ m_importer->SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT);
+}
+
+bool AssimpImporter::load(const QString &filename)
+{
+ uint flags = aiProcess_Triangulate | aiProcess_SortByPType
+ | aiProcess_JoinIdenticalVertices
+ | aiProcess_GenSmoothNormals
+ | aiProcess_GenUVCoords
+ | aiProcess_FlipUVs;
+
+ if (opts.genTangents)
+ flags |= aiProcess_CalcTangentSpace;
+
+ const aiScene *scene = m_importer->ReadFile(filename.toUtf8().constData(), flags);
+ if (!scene)
+ return false;
+
+ if (opts.showLog) {
+ qDebug().noquote() << filename
+ << scene->mNumMeshes << "meshes,"
+ << scene->mNumMaterials << "materials,"
+ << scene->mNumTextures << "embedded textures,"
+ << scene->mNumCameras << "cameras,"
+ << scene->mNumLights << "lights,"
+ << scene->mNumAnimations << "animations";
+ qDebug() << "Scene:";
+ printNodes(scene->mRootNode);
+ }
+
+ buildBuffer();
+ parseEmbeddedTextures();
+ parseMaterials();
+ parseCameras();
+ parseScene();
+ parseAnimations();
+
+ return true;
+}
+
+void AssimpImporter::printNodes(const aiNode *node, int level)
+{
+ qDebug().noquote() << QString().fill('-', level * 4) << ai2qt(node->mName) << node->mNumMeshes << "mesh refs";
+ for (uint i = 0; i < node->mNumChildren; ++i)
+ printNodes(node->mChildren[i], level + 1);
+}
+
+template<class T> void copyIndexBuf(T *dst, const aiMesh *src)
+{
+ for (uint j = 0; j < src->mNumFaces; ++j) {
+ const aiFace *f = &src->mFaces[j];
+ if (f->mNumIndices != 3)
+ qFatal("Face %d is not a triangle (index count %d instead of 3)", j, f->mNumIndices);
+ *dst++ = f->mIndices[0];
+ *dst++ = f->mIndices[1];
+ *dst++ = f->mIndices[2];
+ }
+}
+
+static QString newBufferViewName()
+{
+ static int cnt = 0;
+ return QString(QStringLiteral("bufferView_%1")).arg(++cnt);
+}
+
+static QString newAccessorName()
+{
+ static int cnt = 0;
+ return QString(QStringLiteral("accessor_%1")).arg(++cnt);
+}
+
+static QString newMeshName()
+{
+ static int cnt = 0;
+ return QString(QStringLiteral("mesh_%1")).arg(++cnt);
+}
+
+static QString newMaterialName()
+{
+ static int cnt = 0;
+ return QString(QStringLiteral("material_%1")).arg(++cnt);
+}
+
+static QString newTechniqueName()
+{
+ static int cnt = 0;
+ return QString(QStringLiteral("technique_%1")).arg(++cnt);
+}
+
+static QString newTextureName()
+{
+ static int cnt = 0;
+ return QString(QStringLiteral("texture_%1")).arg(++cnt);
+}
+
+static QString newImageName()
+{
+ static int cnt = 0;
+ return QString(QStringLiteral("image_%1")).arg(++cnt);
+}
+
+static QString newShaderName()
+{
+ static int cnt = 0;
+ return QString(QStringLiteral("shader_%1")).arg(++cnt);
+}
+
+static QString newProgramName()
+{
+ static int cnt = 0;
+ return QString(QStringLiteral("program_%1")).arg(++cnt);
+}
+
+static QString newNodeName()
+{
+ static int cnt = 0;
+ return QString(QStringLiteral("node_%1")).arg(++cnt);
+}
+
+static QString newAnimationName()
+{
+ static int cnt = 0;
+ return QString(QStringLiteral("animation_%1")).arg(++cnt);
+}
+
+template<class T> void calcBB(QVector<float> &minVal, QVector<float> &maxVal, T *data, int vertexCount, int compCount)
+{
+ minVal.resize(compCount);
+ maxVal.resize(compCount);
+ for (int i = 0; i < vertexCount; ++i) {
+ for (int j = 0; j < compCount; ++j) {
+ if (i == 0) {
+ minVal[j] = maxVal[j] = data[i][j];
+ } else {
+ if (data[i][j] < minVal[j])
+ minVal[j] = data[i][j];
+ if (data[i][j] > maxVal[j])
+ maxVal[j] = data[i][j];
+ }
+ }
+ }
+}
+
+// One buffer per importer (scene).
+// Two buffer views (array, index) + three or more accessors per mesh.
+
+void AssimpImporter::buildBuffer()
+{
+ m_buffer.clear();
+ m_meshInfo.clear();
+
+ if (opts.showLog)
+ qDebug() << "Meshes:";
+
+ const aiScene *sc = scene();
+ for (uint i = 0; i < sc->mNumMeshes; ++i) {
+ aiMesh *m = sc->mMeshes[i];
+ MeshInfo meshInfo;
+ meshInfo.originalName = ai2qt(m->mName);
+ meshInfo.name = newMeshName();
+ meshInfo.materialIndex = m->mMaterialIndex;
+
+ aiVector3D *vertices = m->mVertices;
+ aiVector3D *normals = m->mNormals;
+ aiVector3D *textureCoords = m->mTextureCoords[0];
+ aiColor4D *colors = m->mColors[0];
+ aiVector3D *tangents = m->mTangents;
+
+ if (opts.scale != 1) {
+ for (uint j = 0; j < m->mNumVertices; ++j) {
+ vertices[j].x *= opts.scale;
+ vertices[j].y *= opts.scale;
+ vertices[j].z *= opts.scale;
+ }
+ }
+
+ // Vertex (3), Normal (3), Coord? (2), Color? (4), Tangent? (3)
+ uint stride = 3 + 3 + (textureCoords ? 2 : 0) + (colors ? 4 : 0) + (tangents ? 3 : 0);
+ QByteArray vertexBuf;
+ vertexBuf.resize(stride * m->mNumVertices * sizeof(float));
+ float *p = reinterpret_cast<float *>(vertexBuf.data());
+
+ if (opts.interleave) {
+ for (uint j = 0; j < m->mNumVertices; ++j) {
+ // Vertex
+ *p++ = vertices[j].x;
+ *p++ = vertices[j].y;
+ *p++ = vertices[j].z;
+
+ // Normal
+ *p++ = normals[j].x;
+ *p++ = normals[j].y;
+ *p++ = normals[j].z;
+
+ // Coord
+ if (textureCoords) {
+ *p++ = textureCoords[j].x;
+ *p++ = textureCoords[j].y;
+ }
+
+ // Color
+ if (colors) {
+ *p++ = colors[j].r;
+ *p++ = colors[j].g;
+ *p++ = colors[j].b;
+ *p++ = colors[j].a;
+ }
+
+ // Tangent
+ if (tangents) {
+ *p++ = tangents[j].x;
+ *p++ = tangents[j].y;
+ *p++ = tangents[j].z;
+ }
+ }
+ } else {
+ // Vertex
+ for (uint j = 0; j < m->mNumVertices; ++j) {
+ *p++ = vertices[j].x;
+ *p++ = vertices[j].y;
+ *p++ = vertices[j].z;
+ }
+
+ // Normal
+ for (uint j = 0; j < m->mNumVertices; ++j) {
+ *p++ = normals[j].x;
+ *p++ = normals[j].y;
+ *p++ = normals[j].z;
+ }
+
+ // Coord
+ if (textureCoords) {
+ for (uint j = 0; j < m->mNumVertices; ++j) {
+ *p++ = textureCoords[j].x;
+ *p++ = textureCoords[j].y;
+ }
+ }
+
+ // Color
+ if (colors) {
+ for (uint j = 0; j < m->mNumVertices; ++j) {
+ *p++ = colors[j].r;
+ *p++ = colors[j].g;
+ *p++ = colors[j].b;
+ *p++ = colors[j].a;
+ }
+ }
+
+ // Tangent
+ if (tangents) {
+ for (uint j = 0; j < m->mNumVertices; ++j) {
+ *p++ = tangents[j].x;
+ *p++ = tangents[j].y;
+ *p++ = tangents[j].z;
+ }
+ }
+ }
+
+ MeshInfo::BufferView vertexBufView;
+ vertexBufView.name = newBufferViewName();
+ vertexBufView.length = vertexBuf.size();
+ vertexBufView.offset = m_buffer.size();
+ vertexBufView.componentType = GLT_FLOAT;
+ vertexBufView.target = GLT_ARRAY_BUFFER;
+ meshInfo.views.append(vertexBufView);
+
+ QByteArray indexBuf;
+ uint indexCount = m->mNumFaces * 3;
+ if (indexCount >= USHRT_MAX) {
+ indexBuf.resize(indexCount * sizeof(quint32));
+ quint32 *p = reinterpret_cast<quint32 *>(indexBuf.data());
+ copyIndexBuf(p, m);
+ } else {
+ indexBuf.resize(indexCount * sizeof(quint16));
+ quint16 *p = reinterpret_cast<quint16 *>(indexBuf.data());
+ copyIndexBuf(p, m);
+ }
+
+ MeshInfo::BufferView indexBufView;
+ indexBufView.name = newBufferViewName();
+ indexBufView.length = indexBuf.size();
+ indexBufView.offset = vertexBufView.offset + vertexBufView.length;
+ indexBufView.componentType = indexCount >= USHRT_MAX ? GLT_UNSIGNED_INT : GLT_UNSIGNED_SHORT;
+ indexBufView.target = GLT_ELEMENT_ARRAY_BUFFER;
+ meshInfo.views.append(indexBufView);
+
+ MeshInfo::Accessor acc;
+ uint startOffset = 0;
+ // Vertex
+ acc.name = newAccessorName();
+ acc.usage = QStringLiteral("POSITION");
+ acc.bufferView = vertexBufView.name;
+ acc.offset = 0;
+ acc.stride = opts.interleave ? stride * sizeof(float) : 3 * sizeof(float);
+ acc.count = m->mNumVertices;
+ acc.componentType = vertexBufView.componentType;
+ acc.type = QStringLiteral("VEC3");
+ calcBB(acc.minVal, acc.maxVal, vertices, m->mNumVertices, 3);
+ meshInfo.accessors.append(acc);
+ startOffset += opts.interleave ? 3 : 3 * m->mNumVertices;
+ // Normal
+ acc.name = newAccessorName();
+ acc.usage = QStringLiteral("NORMAL");
+ acc.offset = startOffset * sizeof(float);
+ if (!opts.interleave)
+ acc.stride = 3 * sizeof(float);
+ calcBB(acc.minVal, acc.maxVal, normals, m->mNumVertices, 3);
+ meshInfo.accessors.append(acc);
+ startOffset += opts.interleave ? 3 : 3 * m->mNumVertices;
+ // Coord
+ if (textureCoords) {
+ acc.name = newAccessorName();
+ acc.usage = QStringLiteral("TEXCOORD_0");
+ acc.offset = startOffset * sizeof(float);
+ if (!opts.interleave)
+ acc.stride = 2 * sizeof(float);
+ acc.type = QStringLiteral("VEC2");
+ calcBB(acc.minVal, acc.maxVal, textureCoords, m->mNumVertices, 2);
+ meshInfo.accessors.append(acc);
+ startOffset += opts.interleave ? 2 : 2 * m->mNumVertices;
+ }
+ // Color
+ if (colors) {
+ acc.name = newAccessorName();
+ acc.usage = QStringLiteral("COLOR");
+ acc.offset = startOffset * sizeof(float);
+ if (!opts.interleave)
+ acc.stride = 4 * sizeof(float);
+ acc.type = QStringLiteral("VEC4");
+ calcBB(acc.minVal, acc.maxVal, colors, m->mNumVertices, 4);
+ meshInfo.accessors.append(acc);
+ startOffset += opts.interleave ? 4 : 4 * m->mNumVertices;
+ }
+ // Tangent
+ if (tangents) {
+ acc.name = newAccessorName();
+ acc.usage = QStringLiteral("TANGENT");
+ acc.offset = startOffset * sizeof(float);
+ if (!opts.interleave)
+ acc.stride = 3 * sizeof(float);
+ acc.type = QStringLiteral("VEC3");
+ calcBB(acc.minVal, acc.maxVal, tangents, m->mNumVertices, 3);
+ meshInfo.accessors.append(acc);
+ startOffset += opts.interleave ? 3 : 3 * m->mNumVertices;
+ }
+
+ // Index
+ acc.name = newAccessorName();
+ acc.usage = QStringLiteral("INDEX");
+ acc.bufferView = indexBufView.name;
+ acc.offset = 0;
+ acc.stride = 0;
+ acc.count = indexCount;
+ acc.componentType = indexBufView.componentType;
+ acc.type = QStringLiteral("SCALAR");
+ acc.minVal = acc.maxVal = QVector<float>();
+ meshInfo.accessors.append(acc);
+
+ if (opts.showLog) {
+ qDebug().noquote() << "#" << i << "(" << meshInfo.name << "/" << meshInfo.originalName << ")"
+ << m->mNumVertices << "vertices,"
+ << m->mNumFaces << "faces," << stride << "bytes per vertex,"
+ << vertexBuf.size() << "vertex bytes," << indexBuf.size() << "index bytes";
+ if (opts.scale != 1)
+ qDebug() << " scaled by" << opts.scale;
+ if (!opts.interleave)
+ qDebug() << " non-interleaved layout";
+ QStringList sl;
+ foreach (const MeshInfo::BufferView &bv, meshInfo.views) sl << bv.name;
+ qDebug() << " buffer views:" << sl;
+ sl.clear();
+ foreach (const MeshInfo::Accessor &acc, meshInfo.accessors) sl << acc.name;
+ qDebug() << " accessors:" << sl;
+ qDebug() << " material: #" << meshInfo.materialIndex;
+ }
+
+ m_buffer.append(vertexBuf);
+ m_buffer.append(indexBuf);
+
+ m_meshInfo.insert(i, meshInfo);
+ }
+
+ if (opts.showLog)
+ qDebug().noquote() << "Total buffer size" << m_buffer.size();
+}
+
+void AssimpImporter::parseEmbeddedTextures()
+{
+#ifdef HAS_QIMAGE
+ m_embeddedTextures.clear();
+
+ const aiScene *sc = scene();
+ if (opts.showLog && sc->mNumTextures)
+ qDebug() << "Embedded textures:";
+
+ for (uint i = 0; i < sc->mNumTextures; ++i) {
+ aiTexture *t = sc->mTextures[i];
+ QImage img;
+ if (t->mHeight == 0) {
+ img = QImage::fromData(reinterpret_cast<uchar *>(t->pcData), t->mWidth);
+ } else {
+ uint sz = t->mWidth * t->mHeight;
+ QByteArray data;
+ data.resize(sz * 4);
+ uchar *p = reinterpret_cast<uchar *>(data.data());
+ for (uint j = 0; j < sz; ++j) {
+ *p++ = t->pcData[j].r;
+ *p++ = t->pcData[j].g;
+ *p++ = t->pcData[j].b;
+ *p++ = t->pcData[j].a;
+ }
+ img = QImage(reinterpret_cast<const uchar *>(data.constData()), t->mWidth, t->mHeight, QImage::Format_RGBA8888);
+ img.detach();
+ }
+ QString name;
+ static int imgCnt = 0;
+ name = QString(QStringLiteral("texture_%1.png")).arg(++imgCnt);
+ QString embeddedTextureRef = QStringLiteral("*") + QString::number(i); // see AI_MAKE_EMBEDDED_TEXNAME
+ m_embeddedTextures.insert(embeddedTextureRef, EmbeddedTextureInfo(name, img));
+ if (opts.showLog)
+ qDebug().noquote() << "#" << i << name << img;
+ }
+#else
+ if (scene()->mNumTextures)
+ qWarning() << "WARNING: No image support, ignoring" << scene()->mNumTextures << "embedded textures";
+#endif
+}
+
+void AssimpImporter::parseMaterials()
+{
+ m_materialInfo.clear();
+ m_externalTextures.clear();
+
+ if (opts.showLog)
+ qDebug() << "Materials:";
+
+ const aiScene *sc = scene();
+ for (uint i = 0; i < sc->mNumMaterials; ++i) {
+ const aiMaterial *mat = sc->mMaterials[i];
+ MaterialInfo matInfo;
+ matInfo.name = newMaterialName();
+
+ aiString s;
+ if (mat->Get(AI_MATKEY_NAME, s) == aiReturn_SUCCESS)
+ matInfo.originalName = ai2qt(s);
+
+ aiColor4D color;
+ if (mat->Get(AI_MATKEY_COLOR_DIFFUSE, color) == aiReturn_SUCCESS)
+ matInfo.m_colors.insert("diffuse", QVector<float>() << color.r << color.g << color.b << color.a);
+ if (mat->Get(AI_MATKEY_COLOR_SPECULAR, color) == aiReturn_SUCCESS)
+ matInfo.m_colors.insert("specular", QVector<float>() << color.r << color.g << color.b);
+ if (mat->Get(AI_MATKEY_COLOR_AMBIENT, color) == aiReturn_SUCCESS)
+ matInfo.m_colors.insert("ambient", QVector<float>() << color.r << color.g << color.b);
+
+ float f;
+ if (mat->Get(AI_MATKEY_SHININESS, f) == aiReturn_SUCCESS)
+ matInfo.m_values.insert("shininess", f);
+
+ if (mat->GetTexture(aiTextureType_DIFFUSE, 0, &s) == aiReturn_SUCCESS)
+ matInfo.m_textures.insert("diffuse", ai2qt(s));
+ if (mat->GetTexture(aiTextureType_SPECULAR, 0, &s) == aiReturn_SUCCESS)
+ matInfo.m_textures.insert("specular", ai2qt(s));
+ if (mat->GetTexture(aiTextureType_NORMALS, 0, &s) == aiReturn_SUCCESS)
+ matInfo.m_textures.insert("normal", ai2qt(s));
+
+ QHash<QByteArray, QString>::iterator texIt = matInfo.m_textures.begin();
+ while (texIt != matInfo.m_textures.end()) {
+ // Map embedded texture references to real files.
+ if (texIt->startsWith('*'))
+ *texIt = m_embeddedTextures[*texIt].name;
+ else
+ m_externalTextures.insert(*texIt);
+ ++texIt;
+ }
+
+ m_materialInfo.insert(i, matInfo);
+
+ if (opts.showLog) {
+ qDebug().noquote() << "#" << i << "(" << matInfo.name << "/" << matInfo.originalName << ")";
+ qDebug() << " colors:" << matInfo.m_colors;
+ qDebug() << " values:" << matInfo.m_values;
+ qDebug() << " textures:" << matInfo.m_textures;
+ }
+ }
+}
+
+void AssimpImporter::parseCameras()
+{
+ m_cameraInfo.clear();
+
+ if (opts.showLog)
+ qDebug() << "Cameras:";
+
+ const aiScene *sc = scene();
+ for (uint i = 0; i < sc->mNumCameras; ++i) {
+ const aiCamera *cam = sc->mCameras[i];
+ QString name = ai2qt(cam->mName);
+ CameraInfo c;
+
+ c.name = name + QStringLiteral("_cam");
+ c.aspectRatio = qFuzzyIsNull(cam->mAspect) ? 1.5f : cam->mAspect;
+ c.yfov = qRadiansToDegrees(cam->mHorizontalFOV);
+ if (c.yfov < 10) // this can't be right
+ c.yfov = 45;
+ c.znear = cam->mClipPlaneNear;
+ c.zfar = cam->mClipPlaneFar;
+
+ // Collada / glTF cameras point in -Z by default, the rest is in the
+ // node matrix, no separate look-at params given here.
+
+ m_cameraInfo.insert(name, c);
+
+ if (opts.showLog)
+ qDebug().noquote() << "#" << i << "(" << name << ")" << c.aspectRatio << c.yfov << c.znear << c.zfar;
+ }
+}
+
+void AssimpImporter::parseNode(Importer::Node *dst, const aiNode *src)
+{
+ dst->name = ai2qt(src->mName);
+ dst->uniqueName = newNodeName();
+ for (uint j = 0; j < src->mNumChildren; ++j) {
+ Node *c = new Node;
+ parseNode(c, src->mChildren[j]);
+ dst->children << c;
+ }
+ dst->transformation = ai2qt(src->mTransformation);
+ for (uint j = 0; j < src->mNumMeshes; ++j)
+ dst->meshes << src->mMeshes[j];
+}
+
+void AssimpImporter::parseScene()
+{
+ delNode(m_rootNode);
+ const aiScene *sc = scene();
+ m_rootNode = new Node;
+ parseNode(m_rootNode, sc->mRootNode);
+}
+
+void AssimpImporter::addKeyFrame(QVector<KeyFrame> &keyFrames, float t, aiVector3D *vt, aiQuaternion *vr, aiVector3D *vs)
+{
+ KeyFrame kf;
+ int idx = -1;
+ for (int i = 0; i < keyFrames.count(); ++i) {
+ if (qFuzzyCompare(keyFrames[i].t, t)) {
+ kf = keyFrames[i];
+ idx = i;
+ break;
+ }
+ }
+
+ kf.t = t;
+ if (vt) {
+ kf.transValid = true;
+ kf.trans = QVector<float>() << vt->x << vt->y << vt->z;
+ }
+ if (vr) {
+ kf.rotValid = true;
+ kf.rot = QVector<float>() << vr->w << vr->x << vr->y << vr->z;
+ }
+ if (vs) {
+ kf.scaleValid = true;
+ kf.scale = QVector<float>() << vs->x << vs->y << vs->z;
+ }
+
+ if (idx >= 0)
+ keyFrames[idx] = kf;
+ else
+ keyFrames.append(kf);
+}
+
+void AssimpImporter::parseAnimations()
+{
+ const aiScene *sc = scene();
+ if (opts.showLog && sc->mNumAnimations)
+ qDebug() << "Animations:";
+
+ for (uint i = 0; i < sc->mNumAnimations; ++i) {
+ const aiAnimation *anim = sc->mAnimations[i];
+
+ // Only care about node animations.
+ for (uint j = 0; j < anim->mNumChannels; ++j) {
+ const aiNodeAnim *a = anim->mChannels[j];
+ AnimationInfo animInfo;
+ QVector<KeyFrame> keyFrames;
+
+ if (opts.showLog)
+ qDebug().noquote() << ai2qt(anim->mName) << "->" << ai2qt(a->mNodeName);
+
+ // Target values in the keyframes are local absolute (relative to parent, like node.matrix).
+ for (uint kf = 0; kf < a->mNumPositionKeys; ++kf) {
+ float t = float(a->mPositionKeys[kf].mTime);
+ aiVector3D v = a->mPositionKeys[kf].mValue;
+ animInfo.hasTranslation = true;
+ addKeyFrame(keyFrames, t, &v, Q_NULLPTR, Q_NULLPTR);
+ }
+ for (uint kf = 0; kf < a->mNumRotationKeys; ++kf) {
+ float t = float(a->mRotationKeys[kf].mTime);
+ aiQuaternion v = a->mRotationKeys[kf].mValue;
+ animInfo.hasRotation = true;
+ addKeyFrame(keyFrames, t, Q_NULLPTR, &v, Q_NULLPTR);
+ }
+ for (uint kf = 0; kf < a->mNumScalingKeys; ++kf) {
+ float t = float(a->mScalingKeys[kf].mTime);
+ aiVector3D v = a->mScalingKeys[kf].mValue;
+ animInfo.hasScale = true;
+ addKeyFrame(keyFrames, t, Q_NULLPTR, Q_NULLPTR, &v);
+ }
+
+ // Here we should ideally get rid of non-animated properties (that
+ // just set the t-r-s value from node.matrix in every frame) but
+ // let's leave that as a future exercise.
+
+ if (!keyFrames.isEmpty()) {
+ animInfo.name = ai2qt(anim->mName);
+ QString nodeName = ai2qt(a->mNodeName); // have to map to our generated, unique node names
+ const Node *targetNode = findNode(m_rootNode, nodeName);
+ if (targetNode)
+ animInfo.targetNode = targetNode->uniqueName;
+ else
+ qWarning().noquote() << "ERROR: Cannot find target node" << nodeName << "for animation" << animInfo.name;
+ animInfo.keyFrames = keyFrames;
+ m_animations << animInfo;
+
+ if (opts.showLog) {
+ foreach (const KeyFrame &kf, keyFrames) {
+ QString msg;
+ QTextStream s(&msg);
+ s << " @ " << kf.t;
+ if (kf.transValid)
+ s << " T=(" << kf.trans[0] << ", " << kf.trans[1] << ", " << kf.trans[2] << ")";
+ if (kf.rotValid)
+ s << " R=(w=" << kf.rot[0] << ", " << kf.rot[1] << ", " << kf.rot[2] << ", " << kf.rot[3] << ")";
+ if (kf.scaleValid)
+ s << " S=(" << kf.scale[0] << ", " << kf.scale[1] << ", " << kf.scale[2] << ")";
+ qDebug().noquote() << msg;
+ }
+ }
+ }
+ }
+ }
+}
+
+const aiScene *AssimpImporter::scene() const
+{
+ return m_importer->GetScene();
+}
+
+class Exporter
+{
+public:
+ Exporter(Importer *importer) : m_importer(importer) { }
+ virtual ~Exporter() { }
+
+ virtual void save(const QString &inputFilename) = 0;
+
+protected:
+ bool nodeIsUseful(const Importer::Node *n) const;
+ void copyExternalTextures(const QString &inputFilename);
+ void exportEmbeddedTextures();
+ void compressTextures();
+
+ Importer *m_importer;
+ QSet<QString> m_files;
+ QHash<QString, QString> m_compressedTextures;
+};
+
+bool Exporter::nodeIsUseful(const Importer::Node *n) const
+{
+ if (!n->meshes.isEmpty() || m_importer->cameraInfo().contains(n->name))
+ return true;
+
+ foreach (const Importer::Node *c, n->children) {
+ if (nodeIsUseful(c))
+ return true;
+ }
+
+ return false;
+}
+
+void Exporter::copyExternalTextures(const QString &inputFilename)
+{
+ // External textures needs copying only when output dir was specified.
+ if (!opts.outDir.isEmpty()) {
+ foreach (const QString &textureFilename, m_importer->externalTextures()) {
+ QString dst = opts.outDir + textureFilename;
+ QString src = QFileInfo(inputFilename).path() + QStringLiteral("/") + textureFilename;
+ if (QFileInfo(src).absolutePath() != QFileInfo(dst).absolutePath()) {
+ if (opts.showLog)
+ qDebug().noquote() << "Copying" << src << "to" << dst;
+ m_files.insert(QFileInfo(dst).fileName());
+ QFile(src).copy(dst);
+ }
+ }
+ }
+}
+
+void Exporter::exportEmbeddedTextures()
+{
+#ifdef HAS_QIMAGE
+ foreach (const Importer::EmbeddedTextureInfo &embTex, m_importer->embeddedTextures()) {
+ QString fn = opts.outDir + embTex.name;
+ m_files.insert(QFileInfo(fn).fileName());
+ if (opts.showLog)
+ qDebug().noquote() << "Writing" << fn;
+ embTex.image.save(fn);
+ }
+#endif
+}
+
+void Exporter::compressTextures()
+{
+ if (opts.texComp != Options::ETC1)
+ return;
+
+ QStringList imageList;
+ foreach (const QString &textureFilename, m_importer->externalTextures())
+ imageList << opts.outDir + textureFilename;
+ foreach (const Importer::EmbeddedTextureInfo &embTex, m_importer->embeddedTextures())
+ imageList << opts.outDir + embTex.name;
+
+ foreach (const QString &filename, imageList) {
+ if (QFileInfo(filename).suffix().toLower() != QStringLiteral("png"))
+ continue;
+ QByteArray cmd = QByteArrayLiteral("etc1tool ");
+ cmd += filename.toUtf8();
+ qDebug().noquote() << "Invoking" << cmd;
+ // No QProcess in bootstrap
+ if (system(cmd.constData()) == -1) {
+ qWarning() << "ERROR: Failed to launch etc1tool";
+ } else {
+ QString src = QFileInfo(filename).fileName();
+ QString dst = QFileInfo(src).baseName() + QStringLiteral(".pkm");
+ m_compressedTextures.insert(src, dst);
+ m_files.remove(src);
+ m_files.insert(dst);
+ }
+ }
+}
+
+class GltfExporter : public Exporter
+{
+public:
+ GltfExporter(Importer *importer);
+ void save(const QString &inputFilename) Q_DECL_OVERRIDE;
+
+private:
+ struct ProgramInfo {
+ struct Param {
+ Param() : type(0) { }
+ Param(QString name, QString nameInShader, QString semantic, uint type)
+ : name(name), nameInShader(nameInShader), semantic(semantic), type(type) { }
+ QString name;
+ QString nameInShader;
+ QString semantic;
+ uint type;
+ };
+ QString vertShader;
+ QString fragShader;
+ QVector<Param> attributes;
+ QVector<Param> uniforms;
+ };
+
+ void writeShader(const QString &src, const QString &dst, const QVector<QPair<QByteArray, QByteArray> > &substTab);
+ QString exportNode(const Importer::Node *n, QJsonObject &nodes);
+ void exportMaterials(QJsonObject &materials, QHash<QString, QString> *textureNameMap);
+ void exportParameter(QJsonObject &dst, const QVector<ProgramInfo::Param> &params);
+ void exportTechniques(QJsonObject &obj, const QString &basename);
+ void exportAnimations(QJsonObject &obj, QVector<Importer::BufferInfo> &bufList,
+ QVector<Importer::MeshInfo::BufferView> &bvList,
+ QVector<Importer::MeshInfo::Accessor> &accList);
+ void initShaderInfo();
+ ProgramInfo *chooseProgram(uint materialIndex);
+
+ QJsonObject m_obj;
+ QJsonDocument m_doc;
+ QVector<ProgramInfo> m_progs;
+
+ struct TechniqueInfo {
+ TechniqueInfo() : opaque(true), prog(Q_NULLPTR) { }
+ TechniqueInfo(const QString &name, bool opaque, ProgramInfo *prog)
+ : name(name)
+ , opaque(opaque)
+ , prog(prog)
+ {
+ coreName = name + QStringLiteral("_core");
+ gl2Name = name + QStringLiteral("_gl2");
+ }
+ QString name;
+ QString coreName;
+ QString gl2Name;
+ bool opaque;
+ ProgramInfo *prog;
+ };
+ QVector<TechniqueInfo> m_techniques;
+ QSet<ProgramInfo *> m_usedPrograms;
+
+ QVector<QPair<QByteArray, QByteArray> > m_subst_es2;
+ QVector<QPair<QByteArray, QByteArray> > m_subst_core;
+
+ QHash<QString, bool> m_imageHasAlpha;
+};
+
+GltfExporter::GltfExporter(Importer *importer)
+ : Exporter(importer)
+{
+ initShaderInfo();
+}
+
+struct Shader {
+ const char *name;
+ const char *text;
+} shaders[] = {
+ {
+ "color.vert",
+"$VERSION\n"
+"$ATTRIBUTE vec3 vertexPosition;\n"
+"$ATTRIBUTE vec3 vertexNormal;\n"
+"$VVARYING vec3 position;\n"
+"$VVARYING vec3 normal;\n"
+"uniform mat4 projection;\n"
+"uniform mat4 modelView;\n"
+"uniform mat3 modelViewNormal;\n"
+"void main()\n"
+"{\n"
+" normal = normalize( modelViewNormal * vertexNormal );\n"
+" position = vec3( modelView * vec4( vertexPosition, 1.0 ) );\n"
+" gl_Position = projection * modelView * vec4( vertexPosition, 1.0 );\n"
+"}\n"
+ },
+ {
+ "color.frag",
+"$VERSION\n"
+"uniform $HIGHP vec4 lightPosition;\n"
+"uniform $HIGHP vec3 lightIntensity;\n"
+"uniform $HIGHP vec3 ka;\n"
+"uniform $HIGHP vec4 kd;\n"
+"uniform $HIGHP vec3 ks;\n"
+"uniform $HIGHP float shininess;\n"
+"$FVARYING $HIGHP vec3 position;\n"
+"$FVARYING $HIGHP vec3 normal;\n"
+"$DECL_FRAGCOLOR\n"
+"$HIGHP vec3 adsModel( const $HIGHP vec3 pos, const $HIGHP vec3 n )\n"
+"{\n"
+" $HIGHP vec3 s = normalize( vec3( lightPosition ) - pos );\n"
+" $HIGHP vec3 v = normalize( -pos );\n"
+" $HIGHP vec3 r = reflect( -s, n );\n"
+" $HIGHP float diffuse = max( dot( s, n ), 0.0 );\n"
+" $HIGHP float specular = 0.0;\n"
+" if ( dot( s, n ) > 0.0 )\n"
+" specular = pow( max( dot( r, v ), 0.0 ), shininess );\n"
+" return lightIntensity * ( ka + kd.rgb * diffuse + ks * specular );\n"
+"}\n"
+"void main()\n"
+"{\n"
+" $FRAGCOLOR = vec4( adsModel( position, normalize( normal ) ) * kd.a, kd.a );\n"
+"}\n"
+ },
+ {
+ "diffusemap.vert",
+"$VERSION\n"
+"$ATTRIBUTE vec3 vertexPosition;\n"
+"$ATTRIBUTE vec3 vertexNormal;\n"
+"$ATTRIBUTE vec2 vertexTexCoord;\n"
+"$VVARYING vec3 position;\n"
+"$VVARYING vec3 normal;\n"
+"$VVARYING vec2 texCoord;\n"
+"uniform mat4 projection;\n"
+"uniform mat4 modelView;\n"
+"uniform mat3 modelViewNormal;\n"
+"void main()\n"
+"{\n"
+" texCoord = vertexTexCoord;\n"
+" normal = normalize( modelViewNormal * vertexNormal );\n"
+" position = vec3( modelView * vec4( vertexPosition, 1.0 ) );\n"
+" gl_Position = projection * modelView * vec4( vertexPosition, 1.0 );\n"
+"}\n"
+ },
+ {
+ "diffusemap.frag",
+"$VERSION\n"
+"uniform $HIGHP vec4 lightPosition;\n"
+"uniform $HIGHP vec3 lightIntensity;\n"
+"uniform $HIGHP vec3 ka;\n"
+"uniform $HIGHP vec3 ks;\n"
+"uniform $HIGHP float shininess;\n"
+"uniform sampler2D diffuseTexture;\n"
+"$FVARYING $HIGHP vec3 position;\n"
+"$FVARYING $HIGHP vec3 normal;\n"
+"$FVARYING $HIGHP vec2 texCoord;\n"
+"$DECL_FRAGCOLOR\n"
+"$HIGHP vec4 adsModel( const $HIGHP vec3 pos, const $HIGHP vec3 n )\n"
+"{\n"
+" $HIGHP vec3 s = normalize( vec3( lightPosition ) - pos );\n"
+" $HIGHP vec3 v = normalize( -pos );\n"
+" $HIGHP vec3 r = reflect( -s, n );\n"
+" $HIGHP float diffuse = max( dot( s, n ), 0.0 );\n"
+" $HIGHP float specular = 0.0;\n"
+" if ( dot( s, n ) > 0.0 )\n"
+" specular = pow( max( dot( r, v ), 0.0 ), shininess );\n"
+" $HIGHP vec4 kd = $TEXTURE2D( diffuseTexture, texCoord );\n"
+" return vec4( lightIntensity * ( ka + kd.rgb * diffuse + ks * specular ) * kd.a, kd.a );\n"
+"}\n"
+"void main()\n"
+"{\n"
+" $FRAGCOLOR = adsModel( position, normalize( normal ) );\n"
+"}\n"
+ },
+ {
+ "diffusespecularmap.frag",
+"$VERSION\n"
+"uniform $HIGHP vec4 lightPosition;\n"
+"uniform $HIGHP vec3 lightIntensity;\n"
+"uniform $HIGHP vec3 ka;\n"
+"uniform $HIGHP float shininess;\n"
+"uniform sampler2D diffuseTexture;\n"
+"uniform sampler2D specularTexture;\n"
+"$FVARYING $HIGHP vec3 position;\n"
+"$FVARYING $HIGHP vec3 normal;\n"
+"$FVARYING $HIGHP vec2 texCoord;\n"
+"$DECL_FRAGCOLOR\n"
+"$HIGHP vec4 adsModel( const in $HIGHP vec3 pos, const in $HIGHP vec3 n )\n"
+"{\n"
+" $HIGHP vec3 s = normalize( vec3( lightPosition ) - pos );\n"
+" $HIGHP vec3 v = normalize( -pos );\n"
+" $HIGHP vec3 r = reflect( -s, n );\n"
+" $HIGHP float diffuse = max( dot( s, n ), 0.0 );\n"
+" $HIGHP float specular = 0.0;\n"
+" if ( dot( s, n ) > 0.0 )\n"
+" specular = ( shininess / ( 8.0 * 3.14 ) ) * pow( max( dot( r, v ), 0.0 ), shininess );\n"
+" $HIGHP vec4 kd = $TEXTURE2D( diffuseTexture, texCoord );\n"
+" $HIGHP vec3 ks = $TEXTURE2D( specularTexture, texCoord );\n"
+" return vec4( lightIntensity * ( ka + kd.rgb * diffuse + ks * specular ) * kd.a, kd.a );\n"
+"}\n"
+"void main()\n"
+"{\n"
+" $FRAGCOLOR = vec4( adsModel( position, normalize( normal ) ), 1.0 );\n"
+"}\n"
+ },
+ {
+ "normaldiffusemap.vert",
+"$VERSION\n"
+"$ATTRIBUTE vec3 vertexPosition;\n"
+"$ATTRIBUTE vec3 vertexNormal;\n"
+"$ATTRIBUTE vec2 vertexTexCoord;\n"
+"$ATTRIBUTE vec4 vertexTangent;\n"
+"$VVARYING vec3 lightDir;\n"
+"$VVARYING vec3 viewDir;\n"
+"$VVARYING vec2 texCoord;\n"
+"uniform mat4 projection;\n"
+"uniform mat4 modelView;\n"
+"uniform mat3 modelViewNormal;\n"
+"uniform vec4 lightPosition;\n"
+"void main()\n"
+"{\n"
+" texCoord = vertexTexCoord;\n"
+" vec3 normal = normalize( modelViewNormal * vertexNormal );\n"
+" vec3 tangent = normalize( modelViewNormal * vertexTangent.xyz );\n"
+" vec3 position = vec3( modelView * vec4( vertexPosition, 1.0 ) );\n"
+" vec3 binormal = normalize( cross( normal, tangent ) );\n"
+" mat3 tangentMatrix = mat3 (\n"
+" tangent.x, binormal.x, normal.x,\n"
+" tangent.y, binormal.y, normal.y,\n"
+" tangent.z, binormal.z, normal.z );\n"
+" vec3 s = vec3( lightPosition ) - position;\n"
+" lightDir = normalize( tangentMatrix * s );\n"
+" vec3 v = -position;\n"
+" viewDir = normalize( tangentMatrix * v );\n"
+" gl_Position = projection * modelView * vec4( vertexPosition, 1.0 );\n"
+"}\n"
+ },
+ {
+ "normaldiffusemap.frag",
+"$VERSION\n"
+"uniform $HIGHP vec3 lightIntensity;\n"
+"uniform $HIGHP vec3 ka;\n"
+"uniform $HIGHP vec3 ks;\n"
+"uniform $HIGHP float shininess;\n"
+"uniform sampler2D diffuseTexture;\n"
+"uniform sampler2D normalTexture;\n"
+"$FVARYING $HIGHP vec3 lightDir;\n"
+"$FVARYING $HIGHP vec3 viewDir;\n"
+"$FVARYING $HIGHP vec2 texCoord;\n"
+"$DECL_FRAGCOLOR\n"
+"$HIGHP vec3 adsModel( const $HIGHP vec3 norm, const $HIGHP vec3 diffuseReflect)\n"
+"{\n"
+" $HIGHP vec3 r = reflect( -lightDir, norm );\n"
+" $HIGHP vec3 ambient = lightIntensity * ka;\n"
+" $HIGHP float sDotN = max( dot( lightDir, norm ), 0.0 );\n"
+" $HIGHP vec3 diffuse = lightIntensity * diffuseReflect * sDotN;\n"
+" $HIGHP vec3 ambientAndDiff = ambient + diffuse;\n"
+" $HIGHP vec3 spec = vec3( 0.0 );\n"
+" if ( sDotN > 0.0 )\n"
+" spec = lightIntensity * ks * pow( max( dot( r, viewDir ), 0.0 ), shininess );\n"
+" return ambientAndDiff + spec;\n"
+"}\n"
+"void main()\n"
+"{\n"
+" $HIGHP vec4 kd = $TEXTURE2D( diffuseTexture, texCoord );\n"
+" $HIGHP vec4 normal = 2.0 * $TEXTURE2D( normalTexture, texCoord ) - vec4( 1.0 );\n"
+" $FRAGCOLOR = vec4( adsModel( normalize( normal.xyz ), kd.rgb) * kd.a, kd.a );\n"
+"}\n"
+ },
+ {
+ "normaldiffusespecularmap.frag",
+"$VERSION\n"
+"uniform $HIGHP vec3 lightIntensity;\n"
+"uniform $HIGHP vec3 ka;\n"
+"uniform $HIGHP float shininess;\n"
+"uniform sampler2D diffuseTexture;\n"
+"uniform sampler2D specularTexture;\n"
+"uniform sampler2D normalTexture;\n"
+"$FVARYING $HIGHP vec3 lightDir;\n"
+"$FVARYING $HIGHP vec3 viewDir;\n"
+"$FVARYING $HIGHP vec2 texCoord;\n"
+"$DECL_FRAGCOLOR\n"
+"$HIGHP vec3 adsModel( const $HIGHP vec3 norm, const $HIGHP vec3 diffuseReflect, const $HIGHP vec3 specular )\n"
+"{\n"
+" $HIGHP vec3 r = reflect( -lightDir, norm );\n"
+" $HIGHP vec3 ambient = lightIntensity * ka;\n"
+" $HIGHP float sDotN = max( dot( lightDir, norm ), 0.0 );\n"
+" $HIGHP vec3 diffuse = lightIntensity * diffuseReflect * sDotN;\n"
+" $HIGHP vec3 ambientAndDiff = ambient + diffuse;\n"
+" $HIGHP vec3 spec = vec3( 0.0 );\n"
+" if ( sDotN > 0.0 )\n"
+" spec = lightIntensity * ( shininess / ( 8.0 * 3.14 ) ) * pow( max( dot( r, viewDir ), 0.0 ), shininess );\n"
+" return (ambientAndDiff + spec * specular.rgb);\n"
+"}\n"
+"void main()\n"
+"{\n"
+" $HIGHP vec4 kd = $TEXTURE2D( diffuseTexture, texCoord );\n"
+" $HIGHP vec3 ks = $TEXTURE2D( specularTexture, texCoord );\n"
+" $HIGHP vec4 normal = 2.0 * $TEXTURE2D( normalTexture, texCoord ) - vec4( 1.0 );\n"
+" $FRAGCOLOR = vec4( adsModel( normalize( normal.xyz ), kd.rgb, ks ) * kd.a, kd.a );\n"
+"}\n"
+ }
+};
+
+void GltfExporter::initShaderInfo()
+{
+ ProgramInfo p;
+
+ p = ProgramInfo();
+ p.vertShader = "color.vert";
+ p.fragShader = "color.frag";
+ p.attributes << ProgramInfo::Param("position", "vertexPosition", "POSITION", GLT_FLOAT_VEC3);
+ p.attributes << ProgramInfo::Param("normal", "vertexNormal", "NORMAL", GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("projection", "projection", "PROJECTION", GLT_FLOAT_MAT4);
+ p.uniforms << ProgramInfo::Param("modelView", "modelView", "MODELVIEW", GLT_FLOAT_MAT4);
+ p.uniforms << ProgramInfo::Param("normalMatrix", "modelViewNormal", "MODELVIEWINVERSETRANSPOSE", GLT_FLOAT_MAT3);
+ p.uniforms << ProgramInfo::Param("lightPosition", "lightPosition", QString(), GLT_FLOAT_VEC4);
+ p.uniforms << ProgramInfo::Param("lightIntensity", "lightIntensity", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("ambient", "ka", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("diffuse", "kd", QString(), GLT_FLOAT_VEC4);
+ p.uniforms << ProgramInfo::Param("specular", "ks", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("shininess", "shininess", QString(), GLT_FLOAT);
+ m_progs << p;
+
+ p = ProgramInfo();
+ p.vertShader = "diffusemap.vert";
+ p.fragShader = "diffusemap.frag";
+ p.attributes << ProgramInfo::Param("position", "vertexPosition", "POSITION", GLT_FLOAT_VEC3);
+ p.attributes << ProgramInfo::Param("normal", "vertexNormal", "NORMAL", GLT_FLOAT_VEC3);
+ p.attributes << ProgramInfo::Param("texcoord0", "vertexTexCoord", "TEXCOORD_0", GLT_FLOAT_VEC2);
+ p.uniforms << ProgramInfo::Param("projection", "projection", "PROJECTION", GLT_FLOAT_MAT4);
+ p.uniforms << ProgramInfo::Param("modelView", "modelView", "MODELVIEW", GLT_FLOAT_MAT4);
+ p.uniforms << ProgramInfo::Param("normalMatrix", "modelViewNormal", "MODELVIEWINVERSETRANSPOSE", GLT_FLOAT_MAT3);
+ p.uniforms << ProgramInfo::Param("lightPosition", "lightPosition", QString(), GLT_FLOAT_VEC4);
+ p.uniforms << ProgramInfo::Param("lightIntensity", "lightIntensity", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("ambient", "ka", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("specular", "ks", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("shininess", "shininess", QString(), GLT_FLOAT);
+ p.uniforms << ProgramInfo::Param("diffuse", "diffuseTexture", QString(), GLT_SAMPLER_2D);
+ m_progs << p;
+
+ p = ProgramInfo();
+ p.vertShader = "diffusemap.vert";
+ p.fragShader = "diffusespecularmap.frag";
+ p.attributes << ProgramInfo::Param("position", "vertexPosition", "POSITION", GLT_FLOAT_VEC3);
+ p.attributes << ProgramInfo::Param("normal", "vertexNormal", "NORMAL", GLT_FLOAT_VEC3);
+ p.attributes << ProgramInfo::Param("texcoord0", "vertexTexCoord", "TEXCOORD_0", GLT_FLOAT_VEC2);
+ p.uniforms << ProgramInfo::Param("projection", "projection", "PROJECTION", GLT_FLOAT_MAT4);
+ p.uniforms << ProgramInfo::Param("modelView", "modelView", "MODELVIEW", GLT_FLOAT_MAT4);
+ p.uniforms << ProgramInfo::Param("normalMatrix", "modelViewNormal", "MODELVIEWINVERSETRANSPOSE", GLT_FLOAT_MAT3);
+ p.uniforms << ProgramInfo::Param("lightPosition", "lightPosition", QString(), GLT_FLOAT_VEC4);
+ p.uniforms << ProgramInfo::Param("lightIntensity", "lightIntensity", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("ambient", "ka", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("shininess", "shininess", QString(), GLT_FLOAT);
+ p.uniforms << ProgramInfo::Param("diffuse", "diffuseTexture", QString(), GLT_SAMPLER_2D);
+ p.uniforms << ProgramInfo::Param("specular", "specularTexture", QString(), GLT_SAMPLER_2D);
+ m_progs << p;
+
+ p = ProgramInfo();
+ p.vertShader = "normaldiffusemap.vert";
+ p.fragShader = "normaldiffusemap.frag";
+ p.attributes << ProgramInfo::Param("position", "vertexPosition", "POSITION", GLT_FLOAT_VEC3);
+ p.attributes << ProgramInfo::Param("normal", "vertexNormal", "NORMAL", GLT_FLOAT_VEC3);
+ p.attributes << ProgramInfo::Param("texcoord0", "vertexTexCoord", "TEXCOORD_0", GLT_FLOAT_VEC2);
+ p.attributes << ProgramInfo::Param("tangent", "vertexTangent", "TANGENT", GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("projection", "projection", "PROJECTION", GLT_FLOAT_MAT4);
+ p.uniforms << ProgramInfo::Param("modelView", "modelView", "MODELVIEW", GLT_FLOAT_MAT4);
+ p.uniforms << ProgramInfo::Param("normalMatrix", "modelViewNormal", "MODELVIEWINVERSETRANSPOSE", GLT_FLOAT_MAT3);
+ p.uniforms << ProgramInfo::Param("lightPosition", "lightPosition", QString(), GLT_FLOAT_VEC4);
+ p.uniforms << ProgramInfo::Param("lightIntensity", "lightIntensity", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("ambient", "ka", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("specular", "ks", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("shininess", "shininess", QString(), GLT_FLOAT);
+ p.uniforms << ProgramInfo::Param("diffuse", "diffuseTexture", QString(), GLT_SAMPLER_2D);
+ p.uniforms << ProgramInfo::Param("normalmap", "normalTexture", QString(), GLT_SAMPLER_2D);
+ m_progs << p;
+
+ p = ProgramInfo();
+ p.vertShader = "normaldiffusemap.vert";
+ p.fragShader = "normaldiffusespecularmap.frag";
+ p.attributes << ProgramInfo::Param("position", "vertexPosition", "POSITION", GLT_FLOAT_VEC3);
+ p.attributes << ProgramInfo::Param("normal", "vertexNormal", "NORMAL", GLT_FLOAT_VEC3);
+ p.attributes << ProgramInfo::Param("texcoord0", "vertexTexCoord", "TEXCOORD_0", GLT_FLOAT_VEC2);
+ p.attributes << ProgramInfo::Param("tangent", "vertexTangent", "TANGENT", GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("projection", "projection", "PROJECTION", GLT_FLOAT_MAT4);
+ p.uniforms << ProgramInfo::Param("modelView", "modelView", "MODELVIEW", GLT_FLOAT_MAT4);
+ p.uniforms << ProgramInfo::Param("normalMatrix", "modelViewNormal", "MODELVIEWINVERSETRANSPOSE", GLT_FLOAT_MAT3);
+ p.uniforms << ProgramInfo::Param("lightPosition", "lightPosition", QString(), GLT_FLOAT_VEC4);
+ p.uniforms << ProgramInfo::Param("lightIntensity", "lightIntensity", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("ambient", "ka", QString(), GLT_FLOAT_VEC3);
+ p.uniforms << ProgramInfo::Param("shininess", "shininess", QString(), GLT_FLOAT);
+ p.uniforms << ProgramInfo::Param("diffuse", "diffuseTexture", QString(), GLT_SAMPLER_2D);
+ p.uniforms << ProgramInfo::Param("specular", "specularTexture", QString(), GLT_SAMPLER_2D);
+ p.uniforms << ProgramInfo::Param("normalmap", "normalTexture", QString(), GLT_SAMPLER_2D);
+ m_progs << p;
+
+ m_subst_es2 << qMakePair(QByteArrayLiteral("$VERSION"), QByteArray());
+ m_subst_es2 << qMakePair(QByteArrayLiteral("$ATTRIBUTE"), QByteArrayLiteral("attribute"));
+ m_subst_es2 << qMakePair(QByteArrayLiteral("$VVARYING"), QByteArrayLiteral("varying"));
+ m_subst_es2 << qMakePair(QByteArrayLiteral("$FVARYING"), QByteArrayLiteral("varying"));
+ m_subst_es2 << qMakePair(QByteArrayLiteral("$TEXTURE2D"), QByteArrayLiteral("texture2D"));
+ m_subst_es2 << qMakePair(QByteArrayLiteral("$DECL_FRAGCOLOR"), QByteArray());
+ m_subst_es2 << qMakePair(QByteArrayLiteral("$FRAGCOLOR"), QByteArrayLiteral("gl_FragColor"));
+ m_subst_es2 << qMakePair(QByteArrayLiteral("$HIGHP"), QByteArrayLiteral("highp"));
+
+ m_subst_core << qMakePair(QByteArrayLiteral("$VERSION"), QByteArrayLiteral("#version 150 core"));
+ m_subst_core << qMakePair(QByteArrayLiteral("$ATTRIBUTE"), QByteArrayLiteral("in"));
+ m_subst_core << qMakePair(QByteArrayLiteral("$VVARYING"), QByteArrayLiteral("out"));
+ m_subst_core << qMakePair(QByteArrayLiteral("$FVARYING"), QByteArrayLiteral("in"));
+ m_subst_core << qMakePair(QByteArrayLiteral("$TEXTURE2D"), QByteArrayLiteral("texture"));
+ m_subst_core << qMakePair(QByteArrayLiteral("$DECL_FRAGCOLOR"), QByteArrayLiteral("out vec4 fragColor;"));
+ m_subst_core << qMakePair(QByteArrayLiteral("$FRAGCOLOR"), QByteArrayLiteral("fragColor"));
+ m_subst_core << qMakePair(QByteArrayLiteral("$HIGHP "), QByteArray());
+}
+
+GltfExporter::ProgramInfo *GltfExporter::chooseProgram(uint materialIndex)
+{
+ Importer::MaterialInfo matInfo = m_importer->materialInfo(materialIndex);
+ const bool hasNormalTexture = matInfo.m_textures.contains("normal");
+ const bool hasSpecularTexture = matInfo.m_textures.contains("specular");
+ const bool hasDiffuseTexture = matInfo.m_textures.contains("diffuse");
+
+ if (hasNormalTexture && !m_importer->allMeshesForMaterialHaveTangents(materialIndex))
+ qWarning() << "WARNING: Tangent vectors not exported while the material requires it. (hint: try -t)";
+
+ if (hasNormalTexture && hasSpecularTexture && hasDiffuseTexture) {
+ if (opts.showLog)
+ qDebug() << "Using program taking diffuse, specular, normal textures";
+ return &m_progs[4];
+ }
+
+ if (hasNormalTexture && hasDiffuseTexture) {
+ if (opts.showLog)
+ qDebug() << "Using program taking diffuse, normal textures";
+ return &m_progs[3];
+ }
+
+ if (hasSpecularTexture && hasDiffuseTexture) {
+ if (opts.showLog)
+ qDebug() << "Using program taking diffuse, specular textures";
+ return &m_progs[2];
+ }
+
+ if (hasDiffuseTexture) {
+ if (opts.showLog)
+ qDebug() << "Using program taking diffuse texture";
+ return &m_progs[1];
+ }
+
+ if (opts.showLog)
+ qDebug() << "Using program without textures";
+ return &m_progs[0];
+}
+
+QString GltfExporter::exportNode(const Importer::Node *n, QJsonObject &nodes)
+{
+ QJsonObject node;
+ node["name"] = n->name;
+ QJsonArray children;
+ foreach (const Importer::Node *c, n->children) {
+ if (nodeIsUseful(c))
+ children << exportNode(c, nodes);
+ }
+ node["children"] = children;
+ QJsonArray matrix;
+ const float *mtxp = n->transformation.constData();
+ for (int j = 0; j < 16; ++j)
+ matrix.append(*mtxp++);
+ node["matrix"] = matrix;
+ QJsonArray meshList;
+ for (int j = 0; j < n->meshes.count(); ++j)
+ meshList.append(m_importer->meshInfo(n->meshes[j]).name);
+ if (!meshList.isEmpty()) {
+ node["meshes"] = meshList;
+ } else {
+ QHash<QString, Importer::CameraInfo> cam = m_importer->cameraInfo();
+ if (cam.contains(n->name))
+ node["camera"] = cam[n->name].name;
+ }
+
+ nodes[n->uniqueName] = node;
+ return n->uniqueName;
+}
+
+static inline QJsonArray col2jsvec(const QVector<float> &color, bool alpha = false)
+{
+ QJsonArray arr;
+ arr << color[0] << color[1] << color[2];
+ if (alpha)
+ arr << color[3];
+ return arr;
+}
+
+static inline QJsonArray vec2jsvec(const QVector<float> &v)
+{
+ QJsonArray arr;
+ for (int i = 0; i < v.count(); ++i)
+ arr << v[i];
+ return arr;
+}
+
+void GltfExporter::exportMaterials(QJsonObject &materials, QHash<QString, QString> *textureNameMap)
+{
+ for (uint i = 0; i < m_importer->materialCount(); ++i) {
+ Importer::MaterialInfo matInfo = m_importer->materialInfo(i);
+ QJsonObject material;
+ material["name"] = matInfo.originalName;
+
+ bool opaque = true;
+ QJsonObject tech;
+ QJsonObject vals;
+ for (QHash<QByteArray, QString>::const_iterator it = matInfo.m_textures.constBegin(); it != matInfo.m_textures.constEnd(); ++it) {
+ if (!textureNameMap->contains(it.value()))
+ textureNameMap->insert(it.value(), newTextureName());
+ QByteArray key = it.key();
+ if (key == QByteArrayLiteral("normal")) // avoid clashing with the vertex normals
+ key = QByteArrayLiteral("normalmap");
+ // alpha is supported for diffuse textures, but have to check the image data to decide if blending is needed
+ if (key == QByteArrayLiteral("diffuse")) {
+ QString imgFn = opts.outDir + it.value();
+ if (m_imageHasAlpha.contains(imgFn)) {
+ if (m_imageHasAlpha[imgFn])
+ opaque = false;
+ } else {
+#ifdef HAS_QIMAGE
+ QImage img(imgFn);
+ if (!img.isNull()) {
+ if (img.hasAlphaChannel()) {
+ for (int y = 0; opaque && y < img.height(); ++y)
+ for (int x = 0; opaque && x < img.width(); ++x)
+ if (qAlpha(img.pixel(x, y)) < 255)
+ opaque = false;
+ }
+ m_imageHasAlpha[imgFn] = !opaque;
+ } else {
+ qWarning() << "WARNING: Cannot determine presence of alpha for" << imgFn;
+ }
+#else
+ qWarning() << "WARNING: No image support, assuming all textures are opaque";
+#endif
+ }
+ }
+ vals[key] = textureNameMap->value(it.value());
+ }
+ for (QHash<QByteArray, float>::const_iterator it = matInfo.m_values.constBegin();
+ it != matInfo.m_values.constEnd(); ++it) {
+ if (vals.contains(it.key()))
+ continue;
+ vals[it.key()] = it.value();
+ }
+ for (QHash<QByteArray, QVector<float> >::const_iterator it = matInfo.m_colors.constBegin();
+ it != matInfo.m_colors.constEnd(); ++it) {
+ if (vals.contains(it.key()))
+ continue;
+ // alpha is supported for the diffuse color. < 1 will enable blending.
+ const bool alpha = it.key() == QStringLiteral("diffuse");
+ if (alpha && it.value()[3] < 1.0f)
+ opaque = false;
+ vals[it.key()] = col2jsvec(it.value(), alpha);
+ }
+ tech["values"] = vals;
+
+ ProgramInfo *prog = chooseProgram(i);
+ TechniqueInfo techniqueInfo;
+ bool needsNewTechnique = true;
+ for (int j = 0; j < m_techniques.count(); ++j) {
+ if (m_techniques[j].prog == prog) {
+ techniqueInfo = m_techniques[j];
+ needsNewTechnique = opaque != techniqueInfo.opaque;
+ }
+ if (!needsNewTechnique)
+ break;
+ }
+ if (needsNewTechnique) {
+ QString techniqueName = newTechniqueName();
+ techniqueInfo = TechniqueInfo(techniqueName, opaque, prog);
+ m_techniques.append(techniqueInfo);
+ m_usedPrograms.insert(prog);
+ }
+
+ if (opts.showLog)
+ qDebug().noquote() << "Material #" << i << "->" << techniqueInfo.name;
+
+ tech["technique"] = techniqueInfo.name;
+ if (opts.genCore) {
+ tech["techniqueCore"] = techniqueInfo.coreName;
+ tech["techniqueGL2"] = techniqueInfo.gl2Name;
+ }
+
+ material["instanceTechnique"] = tech;
+ materials[matInfo.name] = material;
+ }
+}
+
+void GltfExporter::writeShader(const QString &src, const QString &dst, const QVector<QPair<QByteArray, QByteArray> > &substTab)
+{
+ for (size_t i = 0; i < sizeof(shaders) / sizeof(Shader); ++i) {
+ QByteArray name = src.toUtf8();
+ if (!qstrcmp(shaders[i].name, name.constData())) {
+ QString outfn = opts.outDir + dst;
+ QFile outf(outfn);
+ if (outf.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ m_files.insert(QFileInfo(outf.fileName()).fileName());
+ if (opts.showLog)
+ qDebug() << "Writing" << outfn;
+ foreach (const QString &s, QString::fromUtf8(shaders[i].text).split('\n')) {
+ QString line = s;
+ for (int i = 0; i < substTab.count(); ++i)
+ line.replace(substTab[i].first, substTab[i].second);
+ line += QStringLiteral("\n");
+ outf.write(line.toUtf8());
+ }
+ }
+ return;
+ }
+ }
+ qWarning() << "ERROR: No shader found for" << src;
+}
+
+void GltfExporter::exportParameter(QJsonObject &dst, const QVector<ProgramInfo::Param> &params)
+{
+ foreach (const ProgramInfo::Param &param, params) {
+ QJsonObject parameter;
+ parameter["type"] = int(param.type);
+ if (!param.semantic.isEmpty())
+ parameter["semantic"] = param.semantic;
+ if (param.name == QStringLiteral("lightIntensity"))
+ parameter["value"] = QJsonArray() << 1 << 1 << 1;
+ if (param.name == QStringLiteral("lightPosition"))
+ parameter["value"] = QJsonArray() << 0 << 0 << 0 << 1;
+ dst[param.name] = parameter;
+ }
+}
+
+void GltfExporter::exportTechniques(QJsonObject &obj, const QString &basename)
+{
+ QJsonObject shaders;
+ QHash<QString, QString> shaderMap;
+ foreach (ProgramInfo *prog, m_usedPrograms) {
+ QString newName;
+ if (!shaderMap.contains(prog->vertShader)) {
+ QJsonObject vertexShader;
+ vertexShader["type"] = 35633;
+ if (newName.isEmpty())
+ newName = newShaderName();
+ QString key = basename + QStringLiteral("_") + newName + QStringLiteral("_v");
+ QString fn = QString(QStringLiteral("%1.%2")).arg(key).arg("vert");
+ vertexShader["uri"] = fn;
+ writeShader(prog->vertShader, fn, m_subst_es2);
+ if (opts.genCore) {
+ QJsonObject coreVertexShader;
+ QString coreKey = QString(QStringLiteral("%1_core").arg(key));
+ fn = QString(QStringLiteral("%1.%2")).arg(coreKey).arg("vert");
+ coreVertexShader["type"] = 35633;
+ coreVertexShader["uri"] = fn;
+ writeShader(prog->vertShader, fn, m_subst_core);
+ shaders[coreKey] = coreVertexShader;
+ shaderMap.insert(QString(prog->vertShader + QStringLiteral("_core")), coreKey);
+ }
+ shaders[key] = vertexShader;
+ shaderMap.insert(prog->vertShader, key);
+ }
+ if (!shaderMap.contains(prog->fragShader)) {
+ QJsonObject fragmentShader;
+ fragmentShader["type"] = 35632;
+ if (newName.isEmpty())
+ newName = newShaderName();
+ QString key = basename + QStringLiteral("_") + newName + QStringLiteral("_f");
+ QString fn = QString(QStringLiteral("%1.%2")).arg(key).arg("frag");
+ fragmentShader["uri"] = fn;
+ writeShader(prog->fragShader, fn, m_subst_es2);
+ if (opts.genCore) {
+ QJsonObject coreFragmentShader;
+ QString coreKey = QString(QStringLiteral("%1_core").arg(key));
+ fn = QString(QStringLiteral("%1.%2")).arg(coreKey).arg("frag");
+ coreFragmentShader["type"] = 35632;
+ coreFragmentShader["uri"] = fn;
+ writeShader(prog->fragShader, fn, m_subst_core);
+ shaders[coreKey] = coreFragmentShader;
+ shaderMap.insert(QString(prog->fragShader + QStringLiteral("_core")), coreKey);
+ }
+ shaders[key] = fragmentShader;
+ shaderMap.insert(prog->fragShader, key);
+ }
+ }
+ obj["shaders"] = shaders;
+
+ QJsonObject programs;
+ struct ProgramNames
+ {
+ QString name;
+ QString coreName;
+ };
+
+ QHash<ProgramInfo *, ProgramNames> programMap;
+ foreach (ProgramInfo *prog, m_usedPrograms) {
+ QJsonObject program;
+ program["vertexShader"] = shaderMap[prog->vertShader];
+ program["fragmentShader"] = shaderMap[prog->fragShader];
+ QJsonArray attrs;
+ foreach (const ProgramInfo::Param &param, prog->attributes)
+ attrs << param.nameInShader;
+ program["attributes"] = attrs;
+ QString programName = newProgramName();
+ programMap[prog].name = programName;
+ programs[programMap[prog].name] = program;
+ if (opts.genCore) {
+ program["vertexShader"] = shaderMap[QString(prog->vertShader + QStringLiteral("_core"))];
+ program["fragmentShader"] = shaderMap[QString(prog->fragShader + QStringLiteral("_core"))];
+ QJsonArray attrs;
+ foreach (const ProgramInfo::Param &param, prog->attributes)
+ attrs << param.nameInShader;
+ program["attributes"] = attrs;
+ programMap[prog].coreName = programName + QStringLiteral("_core");
+ programs[programMap[prog].coreName] = program;
+ }
+ }
+ obj["programs"] = programs;
+
+ QJsonObject techniques;
+ foreach (const TechniqueInfo &techniqueInfo, m_techniques) {
+ QJsonObject technique;
+ QJsonObject parameters;
+ ProgramInfo *prog = techniqueInfo.prog;
+ exportParameter(parameters, prog->attributes);
+ exportParameter(parameters, prog->uniforms);
+ technique["parameters"] = parameters;
+ technique["pass"] = QStringLiteral("defaultPass");
+ QJsonObject passes;
+ QJsonObject dp;
+ QJsonObject details;
+ details["type"] = QStringLiteral("COLLADA-1.4.1/commonProfile"); // ??
+ QJsonObject commonProfile;
+ QJsonObject extras;
+ extras["doubleSided"] = false;
+ commonProfile["extras"] = extras;
+ commonProfile["lightingModel"] = QStringLiteral("Phong");
+ QJsonArray paramList;
+ foreach (const ProgramInfo::Param &param, prog->attributes)
+ paramList << param.name;
+ foreach (const ProgramInfo::Param &param, prog->uniforms)
+ paramList << param.name;
+ commonProfile["parameters"] = paramList;
+ QJsonObject texcoordBindings;
+ foreach (const ProgramInfo::Param &param, prog->uniforms) {
+ if (param.type == GLT_SAMPLER_2D)
+ texcoordBindings[param.name] = QStringLiteral("TEXCOORD_0");
+ }
+ if (!texcoordBindings.isEmpty())
+ commonProfile["texcoordBindings"] = texcoordBindings;
+ details["commonProfile"] = commonProfile;
+ dp["details"] = details;
+ QJsonObject instanceProgram;
+ instanceProgram["program"] = programMap[prog].name;
+ QJsonObject progAttrs;
+ foreach (const ProgramInfo::Param &param, prog->attributes)
+ progAttrs[param.nameInShader] = param.name;
+ instanceProgram["attributes"] = progAttrs;
+ QJsonObject progUniforms;
+ foreach (const ProgramInfo::Param &param, prog->uniforms)
+ progUniforms[param.nameInShader] = param.name;
+ instanceProgram["uniforms"] = progUniforms;
+ dp["instanceProgram"] = instanceProgram;
+ QJsonObject states;
+ QJsonArray enabledStates;
+ enabledStates << GLT_DEPTH_TEST << GLT_CULL_FACE;
+ if (!techniqueInfo.opaque) {
+ enabledStates << GLT_BLEND;
+ QJsonObject funcs;
+ // GL_ONE, GL_ONE_MINUS_SRC_ALPHA
+ funcs["blendFuncSeparate"] = QJsonArray() << 1 << 771 << 1 << 771;
+ states["functions"] = funcs;
+ }
+ states["enable"] = enabledStates;
+ dp["states"] = states;
+ passes["defaultPass"] = dp;
+ technique["passes"] = passes;
+ techniques[techniqueInfo.name] = technique;
+
+ if (opts.genCore) {
+ //GL2 (same as ES2)
+ techniques[techniqueInfo.gl2Name] = technique;
+
+ //Core
+ instanceProgram["program"] = programMap[prog].coreName;
+ dp["instanceProgram"] = instanceProgram;
+ passes["defaultPass"] = dp;
+ technique["passes"] = passes;
+ techniques[techniqueInfo.coreName] = technique;
+ }
+ }
+ obj["techniques"] = techniques;
+}
+
+void GltfExporter::exportAnimations(QJsonObject &obj,
+ QVector<Importer::BufferInfo> &bufList,
+ QVector<Importer::MeshInfo::BufferView> &bvList,
+ QVector<Importer::MeshInfo::Accessor> &accList)
+{
+ if (m_importer->animations().isEmpty()) {
+ obj["animations"] = QJsonObject();
+ return;
+ }
+
+ QString bvName = newBufferViewName();
+ QByteArray extraData;
+
+ int sz = 0;
+ foreach (const Importer::AnimationInfo &ai, m_importer->animations())
+ sz += ai.keyFrames.count() * (1 + 3 + 4 + 3) * sizeof(float);
+ extraData.resize(sz);
+
+ float *base = reinterpret_cast<float *>(extraData.data());
+ float *p = base;
+
+ QJsonObject animations;
+ foreach (const Importer::AnimationInfo &ai, m_importer->animations()) {
+ QJsonObject animation;
+ animation["name"] = ai.name;
+ animation["count"] = ai.keyFrames.count();
+ QJsonObject samplers;
+ QJsonArray channels;
+
+ if (ai.hasTranslation) {
+ QJsonObject sampler;
+ sampler["input"] = QStringLiteral("TIME");
+ sampler["interpolation"] = QStringLiteral("LINEAR");
+ sampler["output"] = QStringLiteral("translation");
+ samplers["sampler_translation"] = sampler;
+ QJsonObject channel;
+ channel["sampler"] = QStringLiteral("sampler_translation");
+ QJsonObject target;
+ target["id"] = ai.targetNode;
+ target["path"] = QStringLiteral("translation");
+ channel["target"] = target;
+ channels << channel;
+ }
+ if (ai.hasRotation) {
+ QJsonObject sampler;
+ sampler["input"] = QStringLiteral("TIME");
+ sampler["interpolation"] = QStringLiteral("LINEAR");
+ sampler["output"] = QStringLiteral("rotation");
+ samplers["sampler_rotation"] = sampler;
+ QJsonObject channel;
+ channel["sampler"] = QStringLiteral("sampler_rotation");
+ QJsonObject target;
+ target["id"] = ai.targetNode;
+ target["path"] = QStringLiteral("rotation");
+ channel["target"] = target;
+ channels << channel;
+ }
+ if (ai.hasScale) {
+ QJsonObject sampler;
+ sampler["input"] = QStringLiteral("TIME");
+ sampler["interpolation"] = QStringLiteral("LINEAR");
+ sampler["output"] = QStringLiteral("scale");
+ samplers["sampler_scale"] = sampler;
+ QJsonObject channel;
+ channel["sampler"] = QStringLiteral("sampler_scale");
+ QJsonObject target;
+ target["id"] = ai.targetNode;
+ target["path"] = QStringLiteral("scale");
+ channel["target"] = target;
+ channels << channel;
+ }
+
+ animation["samplers"] = samplers;
+ animation["channels"] = channels;
+ QJsonObject parameters;
+
+ // Multiple animations sharing the same data should ideally use the
+ // same accessors. This we unfortunately cannot do due to assimp's/our
+ // own data structures so everything will get its own accessor and data
+ // for now.
+
+ Importer::MeshInfo::Accessor acc;
+ acc.name = newAccessorName();
+ acc.bufferView = bvName;
+ acc.count = ai.keyFrames.count();
+ acc.componentType = GLT_FLOAT;
+ acc.type = QStringLiteral("SCALAR");
+ acc.offset = (p - base) * sizeof(float);
+ foreach (const Importer::KeyFrame &kf, ai.keyFrames)
+ *p++ = kf.t;
+ parameters["TIME"] = acc.name;
+ accList << acc;
+
+ if (ai.hasTranslation) {
+ acc.name = newAccessorName();
+ acc.componentType = GLT_FLOAT;
+ acc.type = QStringLiteral("VEC3");
+ acc.offset = (p - base) * sizeof(float);
+ QVector<float> lastV;
+ foreach (const Importer::KeyFrame &kf, ai.keyFrames) {
+ const QVector<float> *v = kf.transValid ? &kf.trans : &lastV;
+ *p++ = v->at(0);
+ *p++ = v->at(1);
+ *p++ = v->at(2);
+ if (kf.transValid)
+ lastV = *v;
+ }
+ parameters["translation"] = acc.name;
+ accList << acc;
+ }
+ if (ai.hasRotation) {
+ acc.name = newAccessorName();
+ acc.componentType = GLT_FLOAT;
+ acc.type = QStringLiteral("VEC4");
+ acc.offset = (p - base) * sizeof(float);
+ QVector<float> lastV;
+ foreach (const Importer::KeyFrame &kf, ai.keyFrames) {
+ const QVector<float> *v = kf.rotValid ? &kf.rot : &lastV;
+ *p++ = v->at(1); // x
+ *p++ = v->at(2); // y
+ *p++ = v->at(3); // z
+ *p++ = v->at(0); // w
+ if (kf.rotValid)
+ lastV = *v;
+ }
+ parameters["rotation"] = acc.name;
+ accList << acc;
+ }
+ if (ai.hasScale) {
+ acc.name = newAccessorName();
+ acc.componentType = GLT_FLOAT;
+ acc.type = QStringLiteral("VEC3");
+ acc.offset = (p - base) * sizeof(float);
+ QVector<float> lastV;
+ foreach (const Importer::KeyFrame &kf, ai.keyFrames) {
+ const QVector<float> *v = kf.scaleValid ? &kf.scale : &lastV;
+ *p++ = v->at(0);
+ *p++ = v->at(1);
+ *p++ = v->at(2);
+ if (kf.scaleValid)
+ lastV = *v;
+ }
+ parameters["scale"] = acc.name;
+ accList << acc;
+ }
+ animation["parameters"] = parameters;
+
+ animations[newAnimationName()] = animation;
+ }
+ obj["animations"] = animations;
+
+ // Now all the key frame data is in extraData. Append it to the first buffer
+ // and create a single buffer view for it.
+ if (!extraData.isEmpty()) {
+ if (bufList.isEmpty()) {
+ Importer::BufferInfo b;
+ b.name = QStringLiteral("buf");
+ bufList << b;
+ }
+ Importer::BufferInfo &buf(bufList[0]);
+ Importer::MeshInfo::BufferView bv;
+ bv.name = bvName;
+ bv.offset = buf.data.size();
+ bv.length = (p - base) * sizeof(float);
+ bv.componentType = GLT_FLOAT;
+ bvList << bv;
+ extraData.resize(bv.length);
+ buf.data += extraData;
+ if (opts.showLog)
+ qDebug().noquote() << "Animation data in buffer uses" << extraData.size() << "bytes";
+ }
+}
+
+void GltfExporter::save(const QString &inputFilename)
+{
+ if (opts.showLog)
+ qDebug() << "Exporting";
+
+ m_files.clear();
+ m_techniques.clear();
+ m_usedPrograms.clear();
+
+ QFile f;
+ QString basename = QFileInfo(inputFilename).baseName();
+ QString bufNameTempl = basename + QStringLiteral("_%1.bin");
+
+ copyExternalTextures(inputFilename);
+ exportEmbeddedTextures();
+ compressTextures();
+
+ m_obj = QJsonObject();
+
+ QVector<Importer::BufferInfo> bufList = m_importer->buffers();
+ QVector<Importer::MeshInfo::BufferView> bvList = m_importer->bufferViews();
+ QVector<Importer::MeshInfo::Accessor> accList = m_importer->accessors();
+
+ // Animations add data to the buffer so process them first.
+ exportAnimations(m_obj, bufList, bvList, accList);
+
+ QJsonObject asset;
+ asset["generator"] = QString(QStringLiteral("qgltf %1")).arg(QCoreApplication::applicationVersion());
+ asset["version"] = QStringLiteral("0.8");
+ asset["premultipliedAlpha"] = true;
+ m_obj["asset"] = asset;
+
+ for (int i = 0; i < bufList.count(); ++i) {
+ QString bufName = bufNameTempl.arg(i + 1);
+ f.setFileName(opts.outDir + bufName);
+ if (opts.showLog)
+ qDebug().noquote() << (opts.compress ? "Writing (compressed)" : "Writing") << (opts.outDir + bufName);
+ if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ m_files.insert(QFileInfo(f.fileName()).fileName());
+ QByteArray data = bufList[i].data;
+ if (opts.compress)
+ data = qCompress(data);
+ f.write(data);
+ f.close();
+ }
+ }
+
+ QJsonObject buffers;
+ for (int i = 0; i < bufList.count(); ++i) {
+ QJsonObject buffer;
+ buffer["byteLength"] = bufList[i].data.size();
+ buffer["type"] = QStringLiteral("arraybuffer");
+ buffer["uri"] = bufNameTempl.arg(i + 1);
+ if (opts.compress)
+ buffer["compression"] = QStringLiteral("Qt");
+ buffers[bufList[i].name] = buffer;
+ }
+ m_obj["buffers"] = buffers;
+
+ QJsonObject bufferViews;
+ foreach (const Importer::MeshInfo::BufferView &bv, bvList) {
+ QJsonObject bufferView;
+ bufferView["buffer"] = bufList[bv.bufIndex].name;
+ bufferView["byteLength"] = int(bv.length);
+ bufferView["byteOffset"] = int(bv.offset);
+ if (bv.target)
+ bufferView["target"] = int(bv.target);
+ bufferViews[bv.name] = bufferView;
+ }
+ m_obj["bufferViews"] = bufferViews;
+
+ QJsonObject accessors;
+ foreach (const Importer::MeshInfo::Accessor &acc, accList) {
+ QJsonObject accessor;
+ accessor["bufferView"] = acc.bufferView;
+ accessor["byteOffset"] = int(acc.offset);
+ accessor["byteStride"] = int(acc.stride);
+ accessor["count"] = int(acc.count);
+ accessor["componentType"] = int(acc.componentType);
+ accessor["type"] = acc.type;
+ if (!acc.minVal.isEmpty() && !acc.maxVal.isEmpty()) {
+ accessor["min"] = vec2jsvec(acc.minVal);
+ accessor["max"] = vec2jsvec(acc.maxVal);
+ }
+ accessors[acc.name] = accessor;
+ }
+ m_obj["accessors"] = accessors;
+
+ QJsonObject meshes;
+ for (uint i = 0; i < m_importer->meshCount(); ++i) {
+ Importer::MeshInfo meshInfo = m_importer->meshInfo(i);
+ QJsonObject mesh;
+ mesh["name"] = meshInfo.originalName;
+ QJsonArray prims;
+ QJsonObject prim;
+ prim["primitive"] = 4; // triangles
+ QJsonObject attrs;
+ foreach (const Importer::MeshInfo::Accessor &acc, meshInfo.accessors) {
+ if (acc.usage != QStringLiteral("INDEX"))
+ attrs[acc.usage] = acc.name;
+ else
+ prim["indices"] = acc.name;
+ }
+ prim["attributes"] = attrs;
+ prim["material"] = m_importer->materialInfo(meshInfo.materialIndex).name;
+ prims.append(prim);
+ mesh["primitives"] = prims;
+ meshes[meshInfo.name] = mesh;
+ }
+ m_obj["meshes"] = meshes;
+
+ QJsonObject cameras;
+ for (QHash<QString, Importer::CameraInfo>::const_iterator it = m_importer->cameraInfo().constBegin();
+ it != m_importer->cameraInfo().constEnd(); ++it) {
+ QJsonObject camera;
+ QJsonObject persp;
+ persp["aspect_ratio"] = it->aspectRatio;
+ persp["yfov"] = it->yfov;
+ persp["znear"] = it->znear;
+ persp["zfar"] = it->zfar;
+ camera["perspective"] = persp;
+ camera["type"] = QStringLiteral("perspective");
+ cameras[it->name] = camera;
+ }
+ m_obj["cameras"] = cameras;
+
+ QJsonArray sceneNodes;
+ QJsonObject nodes;
+ foreach (const Importer::Node *n, m_importer->rootNode()->children) {
+ if (nodeIsUseful(n))
+ sceneNodes << exportNode(n, nodes);
+ }
+ m_obj["nodes"] = nodes;
+
+ QJsonObject scenes;
+ QJsonObject defaultScene;
+ defaultScene["nodes"] = sceneNodes;
+ scenes["defaultScene"] = defaultScene;
+ m_obj["scenes"] = scenes;
+ m_obj["scene"] = QStringLiteral("defaultScene");
+
+ QJsonObject materials;
+ QHash<QString, QString> textureNameMap;
+ exportMaterials(materials, &textureNameMap);
+ m_obj["materials"] = materials;
+
+ QJsonObject textures;
+ QHash<QString, QString> imageMap; // uri -> key
+ for (QHash<QString, QString>::const_iterator it = textureNameMap.constBegin(); it != textureNameMap.constEnd(); ++it) {
+ QJsonObject texture;
+ if (!imageMap.contains(it.key()))
+ imageMap[it.key()] = newImageName();
+ texture["source"] = imageMap[it.key()];
+ texture["format"] = 6408; // RGBA
+ texture["internalFormat"] = 6408;
+ texture["sampler"] = QStringLiteral("sampler_1");
+ texture["target"] = 3553; // TEXTURE_2D
+ texture["type"] = 5121; // UNSIGNED_BYTE
+ textures[it.value()] = texture;
+ }
+ m_obj["textures"] = textures;
+
+ QJsonObject images;
+ for (QHash<QString, QString>::const_iterator it = imageMap.constBegin(); it != imageMap.constEnd(); ++it) {
+ QJsonObject image;
+ image["uri"] = m_compressedTextures.contains(it.key()) ? m_compressedTextures[it.key()] : it.key();
+ images[it.value()] = image;
+ }
+ m_obj["images"] = images;
+
+ QJsonObject samplers;
+ QJsonObject sampler;
+ sampler["magFilter"] = 9729; // LINEAR
+ sampler["minFilter"] = 9987; // LINEAR_MIPMAP_LINEAR
+ sampler["wrapS"] = 10497; // REPEAT
+ sampler["wrapT"] = 10497;
+ samplers["sampler_1"] = sampler;
+ m_obj["samplers"] = samplers;
+
+ // Just a dummy light, never referenced.
+ QJsonObject lights;
+ QJsonObject light;
+ QJsonObject pointLight;
+ pointLight["color"] = col2jsvec(QVector<float>() << 1 << 1 << 1);
+ light["point"] = pointLight;
+ light["type"] = QStringLiteral("point");
+ lights["light_1"] = light;
+ m_obj["lights"] = lights;
+
+ exportTechniques(m_obj, basename);
+
+ m_doc.setObject(m_obj);
+
+ QString gltfName = opts.outDir + basename + QStringLiteral(".gltf");
+ f.setFileName(gltfName);
+ if (opts.showLog)
+ qDebug().noquote() << (opts.genBin ? "Writing (binary JSON)" : "Writing") << gltfName;
+
+ if (opts.genBin) {
+ if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ m_files.insert(QFileInfo(f.fileName()).fileName());
+ QByteArray json = m_doc.toBinaryData();
+ f.write(json);
+ f.close();
+ }
+ } else {
+ if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
+ m_files.insert(QFileInfo(f.fileName()).fileName());
+ QByteArray json = m_doc.toJson(opts.compact ? QJsonDocument::Compact : QJsonDocument::Indented);
+ f.write(json);
+ f.close();
+ }
+ }
+
+ QString qrcName = opts.outDir + basename + QStringLiteral(".qrc");
+ f.setFileName(qrcName);
+ if (opts.showLog)
+ qDebug().noquote() << "Writing" << qrcName;
+ if (f.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) {
+ QByteArray pre = "<RCC><qresource prefix=\"/models\">\n";
+ QByteArray post = "</qresource></RCC>\n";
+ f.write(pre);
+ foreach (const QString &file,m_files) {
+ QString line = QString(QStringLiteral(" <file>%1</file>\n")).arg(file);
+ f.write(line.toUtf8());
+ }
+ f.write(post);
+ f.close();
+ }
+
+ if (opts.showLog)
+ qDebug() << "Done\n";
+}
+
+int main(int argc, char **argv)
+{
+ QCoreApplication app(argc, argv);
+ app.setApplicationVersion(QStringLiteral("0.1"));
+ app.setApplicationName(QStringLiteral("Qt glTF converter"));
+
+ QCommandLineParser cmdLine;
+ cmdLine.addHelpOption();
+ cmdLine.addVersionOption();
+ QCommandLineOption outDirOpt(QStringLiteral("d"), QStringLiteral("Place all output data into <dir>"), QStringLiteral("dir"));
+ cmdLine.addOption(outDirOpt);
+ QCommandLineOption binOpt(QStringLiteral("b"), QStringLiteral("Store binary JSON data in the .gltf file"));
+ cmdLine.addOption(binOpt);
+ QCommandLineOption compactOpt(QStringLiteral("m"), QStringLiteral("Store compact JSON in the .gltf file"));
+ cmdLine.addOption(compactOpt);
+ QCommandLineOption compOpt(QStringLiteral("c"), QStringLiteral("qCompress() vertex/index data in the .bin file"));
+ cmdLine.addOption(compOpt);
+ QCommandLineOption tangentOpt(QStringLiteral("t"), QStringLiteral("Generate tangent vectors"));
+ cmdLine.addOption(tangentOpt);
+ QCommandLineOption nonInterleavedOpt(QStringLiteral("n"), QStringLiteral("Use non-interleaved buffer layout"));
+ cmdLine.addOption(nonInterleavedOpt);
+ QCommandLineOption scaleOpt(QStringLiteral("e"), QStringLiteral("Scale vertices by the float scale factor <factor>"), QStringLiteral("factor"));
+ cmdLine.addOption(scaleOpt);
+ QCommandLineOption coreOpt(QStringLiteral("g"), QStringLiteral("Generate OpenGL 3.2+ core profile shaders too"));
+ cmdLine.addOption(coreOpt);
+ QCommandLineOption etc1Opt(QStringLiteral("1"), QStringLiteral("Generate ETC1 compressed textures by invoking etc1tool (PNG only)"));
+ cmdLine.addOption(etc1Opt);
+ QCommandLineOption silentOpt(QStringLiteral("s"), QStringLiteral("Silence debug output"));
+ cmdLine.addOption(silentOpt);
+ cmdLine.process(app);
+ opts.outDir = cmdLine.value(outDirOpt);
+ opts.genBin = cmdLine.isSet(binOpt);
+ opts.compact = cmdLine.isSet(compactOpt);
+ opts.compress = cmdLine.isSet(compOpt);
+ opts.genTangents = cmdLine.isSet(tangentOpt);
+ opts.interleave = !cmdLine.isSet(nonInterleavedOpt);
+ opts.scale = 1;
+ if (cmdLine.isSet(scaleOpt)) {
+ bool ok = false;
+ float v;
+ v = cmdLine.value(scaleOpt).toFloat(&ok);
+ if (ok)
+ opts.scale = v;
+ }
+ opts.genCore = cmdLine.isSet(coreOpt);
+ opts.texComp = cmdLine.isSet(etc1Opt) ? Options::ETC1 : Options::NoTextureCompression;
+ opts.showLog = !cmdLine.isSet(silentOpt);
+ if (!opts.outDir.isEmpty()) {
+ if (!opts.outDir.endsWith('/'))
+ opts.outDir.append('/');
+ QDir().mkpath(opts.outDir);
+ }
+
+ AssimpImporter importer;
+ GltfExporter exporter(&importer);
+ foreach (const QString &fn, cmdLine.positionalArguments()) {
+ if (!importer.load(fn)) {
+ qWarning() << "Failed to import" << fn;
+ continue;
+ }
+ exporter.save(fn);
+ }
+
+ return 0;
+}
diff --git a/tools/qgltf/qgltf.prf b/tools/qgltf/qgltf.prf
new file mode 100644
index 000000000..7b4c590fb
--- /dev/null
+++ b/tools/qgltf/qgltf.prf
@@ -0,0 +1,31 @@
+load(resources)
+
+isEmpty(MODELS): error("qgltf must be loaded after specifying MODELS")
+
+QGLTF_DIR = qmodels
+
+qgltf.commands = qgltf -d $$QGLTF_DIR $$QGLTF_PARAMS ${QMAKE_FILE_NAME}
+qgltf.input = MODELS
+qgltf.output = $$QGLTF_DIR/${QMAKE_FILE_BASE}.gltf
+qgltf.CONFIG += no_link target_predeps
+silent {
+ qgltf.commands += -s
+ qgltf.commands = @echo qgltf ${QMAKE_FILE_IN} && $$qgltf.commands
+}
+QMAKE_EXTRA_COMPILERS += qgltf
+
+asset_builder.commands = $$QMAKE_RCC -binary $$QGLTF_DIR/${QMAKE_FILE_BASE}.qrc -o ${QMAKE_FILE_OUT} -compress 9 -threshold 0
+asset_builder.input = MODELS
+asset_builder.output = ${QMAKE_FILE_BASE}.qrb
+asset_builder.CONFIG += no_link target_predeps
+asset_builder.depends = $$QGLTF_DIR/${QMAKE_FILE_BASE}.gltf
+QMAKE_EXTRA_COMPILERS += asset_builder
+
+for (model, MODELS) {
+ base_model = $$basename(model)
+ qrb_model = $$replace(base_model, (.+)\\..+$, \\1.qrb)
+ asset_install.files += $$absolute_path($$qrb_model, $$OUT_PWD)
+}
+asset_install.path = $$target.path
+asset_install.CONFIG += no_check_exist
+INSTALLS += asset_install
diff --git a/tools/qgltf/qgltf.pro b/tools/qgltf/qgltf.pro
new file mode 100644
index 000000000..8f68bb509
--- /dev/null
+++ b/tools/qgltf/qgltf.pro
@@ -0,0 +1,11 @@
+option(host_build)
+
+SOURCES = $$PWD/qgltf.cpp
+
+include(../../src/3rdparty/assimp/assimp.pri)
+
+mkspecs_features.files = $$PWD/qgltf.prf
+mkspecs_features.path = $$[QT_HOST_DATA]/mkspecs/features
+INSTALLS += mkspecs_features
+
+load(qt_tool)
diff --git a/tools/tools.pro b/tools/tools.pro
new file mode 100644
index 000000000..055f41250
--- /dev/null
+++ b/tools/tools.pro
@@ -0,0 +1,3 @@
+TEMPLATE = subdirs
+SUBDIRS = qgltf
+qgltf.CONFIG += host_build