| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt3D module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** 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 https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.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) override; |
| size_t Write(const void *pvBuffer, size_t pSize, size_t pCount) override; |
| aiReturn Seek(size_t pOffset, aiOrigin pOrigin) override; |
| size_t Tell() const override; |
| size_t FileSize() const override; |
| void Flush() override; |
| |
| private: |
| QIODevice *m_device; |
| }; |
| |
| class AssimpIOSystem : public Assimp::IOSystem |
| { |
| public: |
| bool Exists(const char *pFile) const override; |
| char getOsSeparator() const override; |
| Assimp::IOStream *Open(const char *pFile, const char *pMode) override; |
| void Close(Assimp::IOStream *pFile) override; |
| }; |
| |
| 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 |
| } |
| |
| static QIODevice::OpenMode openModeFromText(const char *name) noexcept |
| { |
| static const struct OpenModeMapping { |
| char name[2]; |
| ushort mode; |
| } openModeMapping[] = { |
| { { 'r', 0 }, QIODevice::ReadOnly }, |
| { { 'r', '+' }, QIODevice::ReadWrite }, |
| { { 'w', 0 }, QIODevice::WriteOnly | QIODevice::Truncate }, |
| { { 'w', '+' }, QIODevice::ReadWrite | QIODevice::Truncate }, |
| { { 'a', 0 }, QIODevice::WriteOnly | QIODevice::Append }, |
| { { 'a', '+' }, QIODevice::ReadWrite | QIODevice::Append }, |
| { { 'w', 'b' }, QIODevice::WriteOnly }, |
| { { 'w', 't' }, QIODevice::WriteOnly | QIODevice::Text }, |
| { { 'r', 'b' }, QIODevice::ReadOnly }, |
| { { 'r', 't' }, QIODevice::ReadOnly | QIODevice::Text }, |
| }; |
| |
| for (auto e : openModeMapping) { |
| if (qstrncmp(e.name, name, sizeof(OpenModeMapping::name)) == 0) |
| return static_cast<QIODevice::OpenMode>(e.mode); |
| } |
| return QIODevice::NotOpen; |
| } |
| |
| bool AssimpIOSystem::Exists(const char *pFile) const |
| { |
| return QFileInfo::exists(QString::fromUtf8(pFile)); |
| } |
| |
| 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 QLatin1String cleanedMode = QLatin1String{pMode}.trimmed(); |
| |
| if (const QIODevice::OpenMode openMode = openModeFromText(cleanedMode.data())) { |
| QScopedPointer<QFile> file(new QFile(fileName)); |
| if (file->open(openMode)) |
| return new AssimpIOStream(file.take()); |
| } |
| |
| return 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 commonMat; |
| bool shaders; |
| 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; |
| }; |
| QT_BEGIN_NAMESPACE |
| Q_DECLARE_TYPEINFO(Importer::BufferInfo, Q_MOVABLE_TYPE); |
| Q_DECLARE_TYPEINFO(Importer::MeshInfo::BufferView, Q_MOVABLE_TYPE); |
| Q_DECLARE_TYPEINFO(Importer::MeshInfo::Accessor, Q_MOVABLE_TYPE); |
| Q_DECLARE_TYPEINFO(Importer::MaterialInfo, Q_MOVABLE_TYPE); |
| Q_DECLARE_TYPEINFO(Importer::CameraInfo, Q_MOVABLE_TYPE); |
| Q_DECLARE_TYPEINFO(Importer::EmbeddedTextureInfo, Q_MOVABLE_TYPE); |
| Q_DECLARE_TYPEINFO(Importer::Node, Q_COMPLEX_TYPE); // uses address as identity |
| Q_DECLARE_TYPEINFO(Importer::KeyFrame, Q_MOVABLE_TYPE); |
| Q_DECLARE_TYPEINFO(Importer::AnimationInfo, Q_MOVABLE_TYPE); |
| QT_END_NAMESPACE |
| |
| Importer::Importer() |
| : m_rootNode(nullptr) |
| { |
| } |
| |
| void Importer::delNode(Importer::Node *n) |
| { |
| if (!n) |
| return; |
| for (Importer::Node *c : qAsConst(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 |
| { |
| for (const MeshInfo &mi : m_meshInfo) { |
| if (mi.materialIndex == materialIndex) { |
| bool hasTangents = false; |
| for (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; |
| for (const MeshInfo &mi : m_meshInfo) { |
| for (const MeshInfo::BufferView &v : mi.views) |
| bv << v; |
| } |
| return bv; |
| } |
| |
| QVector<Importer::MeshInfo::Accessor> Importer::accessors() const |
| { |
| QVector<Importer::MeshInfo::Accessor> acc; |
| for (const MeshInfo &mi : m_meshInfo) { |
| for (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 |
| { |
| for (const Node *c : root->children) { |
| if (c->name == originalName) |
| return c; |
| const Node *cn = findNode(c, originalName); |
| if (cn) |
| return cn; |
| } |
| return nullptr; |
| } |
| |
| class AssimpImporter : public Importer |
| { |
| public: |
| AssimpImporter(); |
| |
| bool load(const QString &filename) 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 |
| | aiProcess_FindDegenerates; |
| |
| 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; |
| for (const MeshInfo::BufferView &bv : qAsConst(meshInfo.views)) sl << bv.name; |
| qDebug() << " buffer views:" << sl; |
| sl.clear(); |
| for (const MeshInfo::Accessor &acc : qAsConst(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 = cam->mHorizontalFOV; |
| if (c.yfov < (M_PI / 10.0)) // this can't be right (probably orthographic source camera) |
| c.yfov = float(M_PI / 4.0); |
| 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, nullptr, 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, nullptr, &v, 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, nullptr, 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) { |
| for (const KeyFrame &kf : qAsConst(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; |
| |
| for (const Importer::Node *c : n->children) { |
| if (nodeIsUseful(c)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void Exporter::copyExternalTextures(const QString &inputFilename) |
| { |
| const auto textureFilenames = m_importer->externalTextures(); |
| for (const QString &textureFilename : textureFilenames) { |
| const QString dst = opts.outDir + textureFilename; |
| m_files.insert(QFileInfo(dst).fileName()); |
| // External textures need copying only when output dir was specified. |
| if (!opts.outDir.isEmpty()) { |
| const QString src = QFileInfo(inputFilename).path() + QStringLiteral("/") + textureFilename; |
| if (QFileInfo(src).absolutePath() != QFileInfo(dst).absolutePath()) { |
| if (opts.showLog) |
| qDebug().noquote() << "Copying" << src << "to" << dst; |
| QFile(src).copy(dst); |
| } |
| } |
| } |
| } |
| |
| void Exporter::exportEmbeddedTextures() |
| { |
| #ifdef HAS_QIMAGE |
| const auto embeddedTextures = m_importer->embeddedTextures(); |
| for (const Importer::EmbeddedTextureInfo &embTex : 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; |
| |
| const auto textureFilenames = m_importer->externalTextures(); |
| const auto embeddedTextures = m_importer->embeddedTextures(); |
| QStringList imageList; |
| imageList.reserve(textureFilenames.size() + embeddedTextures.size()); |
| for (const QString &textureFilename : textureFilenames) |
| imageList << opts.outDir + textureFilename; |
| for (const Importer::EmbeddedTextureInfo &embTex : embeddedTextures) |
| imageList << opts.outDir + embTex.name; |
| |
| for (const QString &filename : qAsConst(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) 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 commonTechniqueName; |
| QString vertShader; |
| QString fragShader; |
| QVector<Param> attributes; |
| QVector<Param> uniforms; |
| }; |
| friend class QTypeInfo<ProgramInfo>; |
| friend class QTypeInfo<ProgramInfo::Param>; |
| |
| 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(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; |
| }; |
| friend class QTypeInfo<TechniqueInfo>; |
| 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; |
| }; |
| QT_BEGIN_NAMESPACE |
| Q_DECLARE_TYPEINFO(GltfExporter::ProgramInfo, Q_MOVABLE_TYPE); |
| Q_DECLARE_TYPEINFO(GltfExporter::ProgramInfo::Param, Q_MOVABLE_TYPE); |
| Q_DECLARE_TYPEINFO(GltfExporter::TechniqueInfo, Q_MOVABLE_TYPE); |
| QT_END_NAMESPACE |
| |
| 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 vPosition;\n" |
| "$VVARYING vec3 vNormal;\n" |
| "uniform mat4 projection;\n" |
| "uniform mat4 modelView;\n" |
| "uniform mat3 modelViewNormal;\n" |
| "void main()\n" |
| "{\n" |
| " vNormal = normalize( modelViewNormal * vertexNormal );\n" |
| " vPosition = 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 vPosition;\n" |
| "$FVARYING $HIGHP vec3 vNormal;\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( vPosition, normalize( vNormal ) ) * kd.a, kd.a );\n" |
| "}\n" |
| }, |
| { |
| "diffusemap.vert", |
| "$VERSION\n" |
| "$ATTRIBUTE vec3 vertexPosition;\n" |
| "$ATTRIBUTE vec3 vertexNormal;\n" |
| "$ATTRIBUTE vec2 vertexTexCoord;\n" |
| "$VVARYING vec3 vPosition;\n" |
| "$VVARYING vec3 vNormal;\n" |
| "$VVARYING vec2 vTexCoord;\n" |
| "uniform mat4 projection;\n" |
| "uniform mat4 modelView;\n" |
| "uniform mat3 modelViewNormal;\n" |
| "void main()\n" |
| "{\n" |
| " vTexCoord = vertexTexCoord;\n" |
| " vNormal = normalize( modelViewNormal * vertexNormal );\n" |
| " vPosition = 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 vPosition;\n" |
| "$FVARYING $HIGHP vec3 vNormal;\n" |
| "$FVARYING $HIGHP vec2 vTexCoord;\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, vTexCoord );\n" |
| " return vec4( lightIntensity * ( ka + kd.rgb * diffuse + ks * specular ) * kd.a, kd.a );\n" |
| "}\n" |
| "void main()\n" |
| "{\n" |
| " $FRAGCOLOR = adsModel( vPosition, normalize( vNormal ) );\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 vPosition;\n" |
| "$FVARYING $HIGHP vec3 vNormal;\n" |
| "$FVARYING $HIGHP vec2 vTexCoord;\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, vTexCoord );\n" |
| " $HIGHP vec3 ks = $TEXTURE2D( specularTexture, vTexCoord );\n" |
| " return vec4( lightIntensity * ( ka + kd.rgb * diffuse + ks * specular ) * kd.a, kd.a );\n" |
| "}\n" |
| "void main()\n" |
| "{\n" |
| " $FRAGCOLOR = vec4( adsModel( vPosition, normalize( vNormal ) ), 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.commonTechniqueName = "PHONG"; // diffuse RGBA, specular RGBA |
| 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.commonTechniqueName = "PHONG"; // diffuse texture, specular RGBA |
| 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.commonTechniqueName = "PHONG"; // diffuse texture, specular texture |
| 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.commonTechniqueName = "PHONG"; // diffuse texture, specular RGBA, normalmap texture |
| 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.commonTechniqueName = "PHONG"; // diffuse texture, specular texture, normalmap texture |
| 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; |
| for (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; |
| } |
| |
| static inline void promoteColorsToRGBA(QJsonObject *obj) |
| { |
| QJsonObject::iterator it = obj->begin(), itEnd = obj->end(); |
| while (it != itEnd) { |
| QJsonArray arr = it.value().toArray(); |
| if (arr.count() == 3) { |
| const QString key = it.key(); |
| if (key == QStringLiteral("ambient") |
| || key == QStringLiteral("diffuse") |
| || key == QStringLiteral("specular")) { |
| arr.append(1); |
| *it = arr; |
| } |
| } |
| ++it; |
| } |
| } |
| |
| 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 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); |
| } |
| if (opts.shaders) |
| material["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.shaders) { |
| if (opts.showLog) |
| qDebug().noquote() << "Material #" << i << "->" << techniqueInfo.name; |
| |
| material["technique"] = techniqueInfo.name; |
| if (opts.genCore) { |
| material["techniqueCore"] = techniqueInfo.coreName; |
| material["techniqueGL2"] = techniqueInfo.gl2Name; |
| } |
| } |
| |
| if (opts.commonMat) { |
| // The built-in shaders we output are of little use in practice. |
| // Ideally we want Qt3D's own standard materials in order to have our |
| // models participate in lighting for example. To achieve this, output |
| // a KHR_materials_common block which Qt3D's loader will recognize and |
| // prefer over the shader-based techniques. |
| if (!prog->commonTechniqueName.isEmpty()) { |
| QJsonObject commonMat; |
| commonMat["technique"] = prog->commonTechniqueName; |
| // Set the values as-is. "normalmap" is our own extension, not in the spec. |
| // However, RGB colors have to be promoted to RGBA since the spec uses |
| // vec4, and all types are pre-defined for common material values. |
| promoteColorsToRGBA(&vals); |
| commonMat["values"] = vals; |
| if (!opaque) |
| commonMat["transparent"] = true; |
| QJsonObject extensions; |
| extensions["KHR_materials_common"] = commonMat; |
| material["extensions"] = extensions; |
| } |
| } |
| |
| materials[matInfo.name] = material; |
| } |
| } |
| |
| void GltfExporter::writeShader(const QString &src, const QString &dst, const QVector<QPair<QByteArray, QByteArray> > &substTab) |
| { |
| for (const Shader shader : shaders) { |
| QByteArray name = src.toUtf8(); |
| if (!qstrcmp(shader.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; |
| const auto lines = QString::fromUtf8(shader.text).split('\n'); |
| for (QString line : lines) { |
| for (const auto &subst : substTab) |
| line.replace(subst.first, subst.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) |
| { |
| for (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; |
| } |
| } |
| |
| namespace { |
| struct ProgramNames |
| { |
| QString name; |
| QString coreName; |
| }; |
| } |
| |
| void GltfExporter::exportTechniques(QJsonObject &obj, const QString &basename) |
| { |
| if (!opts.shaders) |
| return; |
| |
| QJsonObject shaders; |
| QHash<QString, QString> shaderMap; |
| for (ProgramInfo *prog : qAsConst(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.vert")).arg(key); |
| 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.vert")).arg(coreKey); |
| 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.frag")).arg(key); |
| 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.frag")).arg(coreKey); |
| 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; |
| QHash<const ProgramInfo *, ProgramNames> programMap; |
| for (const ProgramInfo *prog : qAsConst(m_usedPrograms)) { |
| QJsonObject program; |
| program["vertexShader"] = shaderMap[prog->vertShader]; |
| program["fragmentShader"] = shaderMap[prog->fragShader]; |
| QJsonArray attrs; |
| for (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 + QLatin1String("_core"))]; |
| program["fragmentShader"] = shaderMap[QString(prog->fragShader + QLatin1String("_core"))]; |
| QJsonArray attrs; |
| for (const ProgramInfo::Param ¶m : prog->attributes) { |
| attrs << param.nameInShader; |
| } |
| program["attributes"] = attrs; |
| programMap[prog].coreName = programName + QLatin1String("_core"); |
| programs[programMap[prog].coreName] = program; |
| } |
| } |
| obj["programs"] = programs; |
| |
| QJsonObject techniques; |
| for (const TechniqueInfo &techniqueInfo : qAsConst(m_techniques)) { |
| QJsonObject technique; |
| QJsonObject parameters; |
| const ProgramInfo *prog = techniqueInfo.prog; |
| exportParameter(parameters, prog->attributes); |
| exportParameter(parameters, prog->uniforms); |
| technique["parameters"] = parameters; |
| technique["program"] = programMap[prog].name; |
| QJsonObject progAttrs; |
| for (const ProgramInfo::Param ¶m : prog->attributes) { |
| progAttrs[param.nameInShader] = param.name; |
| } |
| technique["attributes"] = progAttrs; |
| QJsonObject progUniforms; |
| for (const ProgramInfo::Param ¶m : prog->uniforms) { |
| progUniforms[param.nameInShader] = param.name; |
| } |
| technique["uniforms"] = progUniforms; |
| 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; |
| technique["states"] = states; |
| techniques[techniqueInfo.name] = technique; |
| |
| if (opts.genCore) { |
| //GL2 (same as ES2) |
| techniques[techniqueInfo.gl2Name] = technique; |
| |
| //Core |
| technique["program"] = programMap[prog].coreName; |
| 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) |
| { |
| const auto animationInfos = m_importer->animations(); |
| if (animationInfos.empty()) { |
| obj["animations"] = QJsonObject(); |
| return; |
| } |
| |
| QString bvName = newBufferViewName(); |
| QByteArray extraData; |
| |
| int sz = 0; |
| for (const Importer::AnimationInfo &ai : animationInfos) |
| 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; |
| for (const Importer::AnimationInfo &ai : animationInfos) { |
| 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 = uint((p - base) * sizeof(float)); |
| for (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 = uint((p - base) * sizeof(float)); |
| QVector<float> lastV; |
| for (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 = uint((p - base) * sizeof(float)); |
| QVector<float> lastV; |
| for (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 = uint((p - base) * sizeof(float)); |
| QVector<float> lastV; |
| for (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 = uint((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("1.0"); |
| 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; |
| for (const Importer::MeshInfo::BufferView &bv : qAsConst(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; |
| for (const Importer::MeshInfo::Accessor &acc : qAsConst(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) { |
| const Importer::MeshInfo meshInfo = m_importer->meshInfo(i); |
| QJsonObject mesh; |
| mesh["name"] = meshInfo.originalName; |
| QJsonArray prims; |
| QJsonObject prim; |
| prim["mode"] = 4; // triangles |
| QJsonObject attrs; |
| for (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; |
| const auto cameraInfos = m_importer->cameraInfo(); |
| for (const Importer::CameraInfo &camInfo : cameraInfos) { |
| QJsonObject camera; |
| QJsonObject persp; |
| persp["aspect_ratio"] = camInfo.aspectRatio; |
| persp["yfov"] = camInfo.yfov; |
| persp["znear"] = camInfo.znear; |
| persp["zfar"] = camInfo.zfar; |
| camera["perspective"] = persp; |
| camera["type"] = QStringLiteral("perspective"); |
| cameras[camInfo.name] = camera; |
| } |
| m_obj["cameras"] = cameras; |
| |
| QJsonArray sceneNodes; |
| QJsonObject nodes; |
| for (const Importer::Node *n : qAsConst(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"] = 0x1908; // RGBA |
| const bool compressed = m_compressedTextures.contains(it.key()); |
| texture["internalFormat"] = !compressed ? 0x1908 : 0x8D64; // RGBA / ETC1 |
| texture["sampler"] = !compressed ? QStringLiteral("sampler_mip_rep") : QStringLiteral("sampler_nonmip_rep"); |
| 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_mip_rep"] = sampler; |
| // Compressed textures may not support mipmapping with GLES. |
| if (!m_compressedTextures.isEmpty()) { |
| sampler["minFilter"] = 9729; // LINEAR |
| samplers["sampler_nonmip_rep"] = sampler; |
| } |
| m_obj["samplers"] = samplers; |
| |
| exportTechniques(m_obj, basename); |
| |
| m_doc.setObject(m_obj); |
| |
| QString gltfName = opts.outDir + basename + QStringLiteral(".qgltf"); |
| 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); |
| for (const QString &file : qAsConst(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"; |
| } |
| |
| static const char *description = |
| "qgltf uses Assimp to import a variety of 3D model formats " |
| "and export it into fast-to-load, optimized glTF " |
| "assets embedded into Qt resource files.\n\n" |
| "Note: this tool should typically not be invoked directly. Instead, " |
| "let qmake manage it based on QT3D_MODELS in the .pro file.\n\n" |
| "For standard Qt 3D usage the recommended options are -b -S."; |
| |
| int main(int argc, char **argv) |
| { |
| QCoreApplication app(argc, argv); |
| app.setApplicationVersion(QStringLiteral("0.2")); |
| app.setApplicationName(QStringLiteral("Qt glTF converter")); |
| |
| QCommandLineParser cmdLine; |
| cmdLine.addHelpOption(); |
| cmdLine.addVersionOption(); |
| cmdLine.setApplicationDescription(QString::fromUtf8(description)); |
| 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 .qgltf file")); |
| cmdLine.addOption(binOpt); |
| QCommandLineOption compactOpt(QStringLiteral("m"), QStringLiteral("Store compact JSON in the .qgltf 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 noCommonMatOpt(QStringLiteral("T"), QStringLiteral("Do not generate KHR_materials_common block")); |
| cmdLine.addOption(noCommonMatOpt); |
| QCommandLineOption noShadersOpt(QStringLiteral("S"), QStringLiteral("Do not generate shaders/programs/techniques")); |
| cmdLine.addOption(noShadersOpt); |
| 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.commonMat = !cmdLine.isSet(noCommonMatOpt); |
| opts.shaders = !cmdLine.isSet(noShadersOpt); |
| opts.showLog = !cmdLine.isSet(silentOpt); |
| if (!opts.outDir.isEmpty()) { |
| if (!opts.outDir.endsWith('/')) |
| opts.outDir.append('/'); |
| QDir().mkpath(opts.outDir); |
| } |
| |
| const auto fileNames = cmdLine.positionalArguments(); |
| if (fileNames.isEmpty()) |
| cmdLine.showHelp(); |
| |
| AssimpImporter importer; |
| GltfExporter exporter(&importer); |
| for (const QString &fn : fileNames) { |
| if (!importer.load(fn)) { |
| qWarning() << "Failed to import" << fn; |
| continue; |
| } |
| exporter.save(fn); |
| } |
| |
| return 0; |
| } |