diff options
author | Laszlo Agocs <laszlo.agocs@theqtcompany.com> | 2015-08-17 13:50:47 +0200 |
---|---|---|
committer | Laszlo Agocs <laszlo.agocs@theqtcompany.com> | 2015-08-18 11:30:30 +0000 |
commit | cc17811995b382280a65a30fdd93bc92c5c01478 (patch) | |
tree | f4e041b409020f0b4536517c1c9eeaffa2e51d1c /tools | |
parent | ff6eb8b14dd6754c1db6b444d7012601ad6e45ce (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.cpp | 2509 | ||||
-rw-r--r-- | tools/qgltf/qgltf.prf | 31 | ||||
-rw-r--r-- | tools/qgltf/qgltf.pro | 11 | ||||
-rw-r--r-- | tools/tools.pro | 3 |
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> ¶ms); + 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> ¶ms) +{ + foreach (const ProgramInfo::Param ¶m, 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 ¶m, 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 ¶m, 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 ¶m, prog->attributes) + paramList << param.name; + foreach (const ProgramInfo::Param ¶m, prog->uniforms) + paramList << param.name; + commonProfile["parameters"] = paramList; + QJsonObject texcoordBindings; + foreach (const ProgramInfo::Param ¶m, 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 ¶m, prog->attributes) + progAttrs[param.nameInShader] = param.name; + instanceProgram["attributes"] = progAttrs; + QJsonObject progUniforms; + foreach (const ProgramInfo::Param ¶m, 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 |