blob: 860dec3e7078473bf93fd41d740fddbaf0387c4e [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies).
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt3D module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** 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 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.LGPL3 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-3.0.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 (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** 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-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "gltfimporter.h"
#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qjsonarray.h>
#include <QtCore/qjsonobject.h>
#include <QtCore/qmath.h>
#include <QtGui/qvector2d.h>
#include <Qt3DCore/qentity.h>
#include <Qt3DCore/qtransform.h>
#include <Qt3DRender/qcameralens.h>
#include <Qt3DRender/qcamera.h>
#include <Qt3DRender/qalphacoverage.h>
#include <Qt3DRender/qalphatest.h>
#include <Qt3DRender/qblendequation.h>
#include <Qt3DRender/qblendequationarguments.h>
#include <Qt3DRender/qclipplane.h>
#include <Qt3DRender/qcolormask.h>
#include <Qt3DRender/qcullface.h>
#include <Qt3DRender/qdithering.h>
#include <Qt3DRender/qmultisampleantialiasing.h>
#include <Qt3DRender/qpointsize.h>
#include <Qt3DRender/qnodepthmask.h>
#include <Qt3DRender/qdepthrange.h>
#include <Qt3DRender/qdepthtest.h>
#include <Qt3DRender/qseamlesscubemap.h>
#include <Qt3DRender/qstencilmask.h>
#include <Qt3DRender/qstenciloperation.h>
#include <Qt3DRender/qstenciloperationarguments.h>
#include <Qt3DRender/qstenciltest.h>
#include <Qt3DRender/qstenciltestarguments.h>
#include <Qt3DRender/qeffect.h>
#include <Qt3DRender/qfrontface.h>
#include <Qt3DRender/qgeometry.h>
#include <Qt3DRender/qgeometryrenderer.h>
#include <Qt3DRender/qmaterial.h>
#include <Qt3DRender/qgraphicsapifilter.h>
#include <Qt3DRender/qparameter.h>
#include <Qt3DRender/qpolygonoffset.h>
#include <Qt3DRender/qrenderstate.h>
#include <Qt3DRender/qscissortest.h>
#include <Qt3DRender/qshaderprogram.h>
#include <Qt3DRender/qtechnique.h>
#include <Qt3DRender/qtexture.h>
#include <Qt3DRender/qtextureimagedatagenerator.h>
#include <Qt3DRender/qdirectionallight.h>
#include <Qt3DRender/qspotlight.h>
#include <Qt3DRender/qpointlight.h>
#include <Qt3DExtras/qphongmaterial.h>
#include <Qt3DExtras/qphongalphamaterial.h>
#include <Qt3DExtras/qdiffusemapmaterial.h>
#include <Qt3DExtras/qdiffusespecularmapmaterial.h>
#include <Qt3DExtras/qnormaldiffusemapmaterial.h>
#include <Qt3DExtras/qnormaldiffusemapalphamaterial.h>
#include <Qt3DExtras/qnormaldiffusespecularmapmaterial.h>
#include <Qt3DExtras/qgoochmaterial.h>
#include <Qt3DExtras/qpervertexcolormaterial.h>
#include <Qt3DExtras/qmetalroughmaterial.h>
#include <Qt3DExtras/qconemesh.h>
#include <Qt3DExtras/qcuboidmesh.h>
#include <Qt3DExtras/qcylindermesh.h>
#include <Qt3DExtras/qplanemesh.h>
#include <Qt3DExtras/qspheremesh.h>
#include <Qt3DExtras/qtorusmesh.h>
#include <private/qurlhelper_p.h>
#include <private/qloadgltf_p.h>
/**
* glTF 2.0 conformance report
*
* Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0
* Samples: https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0
*
* Most of the reference samples are rendered correctly, with the following exceptions:
*
* 'extensions' and 'extras' are ignored everywhere except in nodes.
*
* asset
* generator, copyright, minVersion: not parsed
* accessors
* min, max, normalized, sparse: not parsed
* animations
* the whole object is not parsed
* buffers
* all parsed
* bufferViews
* all parsed
* cameras
* all parsed
* images
* mimeType, bufferView, name: not parsed
* materials
* emissiveTexture, emissiveFactor: not parsed
* alphaMode, alphaCutoff, doubleSided: not parsed
* texCoord, strength: not parsed
* meshes
* weights: not parsed
* nodes
* skin, weights: not parsed
* samplers
* all parsed
* scenes
* all parsed
* textures
* all parsed
*/
#ifndef qUtf16PrintableImpl // -Impl is a Qt 5.8 feature
# define qUtf16PrintableImpl(string) \
static_cast<const wchar_t*>(static_cast<const void*>(string.utf16()))
#endif
#define KEY_ASSET QLatin1String("asset")
#define KEY_VERSION QLatin1String("version")
#define KEY_CAMERA QLatin1String("camera")
#define KEY_CAMERAS QLatin1String("cameras")
#define KEY_SCENES QLatin1String("scenes")
#define KEY_NODES QLatin1String("nodes")
#define KEY_MESHES QLatin1String("meshes")
#define KEY_CHILDREN QLatin1String("children")
#define KEY_MESH QLatin1String("mesh")
#define KEY_MATRIX QLatin1String("matrix")
#define KEY_ROTATION QLatin1String("rotation")
#define KEY_SCALE QLatin1String("scale")
#define KEY_TRANSLATION QLatin1String("translation")
#define KEY_TYPE QLatin1String("type")
#define KEY_PERSPECTIVE QLatin1String("perspective")
#define KEY_ORTHOGRAPHIC QLatin1String("orthographic")
#define KEY_NAME QLatin1String("name")
#define KEY_COUNT QLatin1String("count")
#define KEY_YFOV QLatin1String("yfov")
#define KEY_ZNEAR QLatin1String("znear")
#define KEY_ZFAR QLatin1String("zfar")
#define KEY_XMAG QLatin1String("xmag")
#define KEY_YMAG QLatin1String("ymag")
#define KEY_MATERIALS QLatin1String("materials")
#define KEY_EXTENSIONS QLatin1String("extensions")
#define KEY_COMMON_MAT QLatin1String("KHR_materials_common")
#define KEY_TECHNIQUE QLatin1String("technique")
#define KEY_VALUES QLatin1String("values")
#define KEY_BUFFERS QLatin1String("buffers")
#define KEY_SHADERS QLatin1String("shaders")
#define KEY_PROGRAMS QLatin1String("programs")
#define KEY_PROGRAM QLatin1String("program")
#define KEY_TECHNIQUES QLatin1String("techniques")
#define KEY_ACCESSORS QLatin1String("accessors")
#define KEY_IMAGES QLatin1String("images")
#define KEY_TEXTURES QLatin1String("textures")
#define KEY_SCENE QLatin1String("scene")
#define KEY_BUFFER QLatin1String("buffer")
#define KEY_TARGET QLatin1String("target")
#define KEY_BYTE_OFFSET QLatin1String("byteOffset")
#define KEY_BYTE_LENGTH QLatin1String("byteLength")
#define KEY_BYTE_STRIDE QLatin1String("byteStride")
#define KEY_PRIMITIVES QLatin1String("primitives")
#define KEY_MODE QLatin1String("mode")
#define KEY_MATERIAL QLatin1String("material")
#define KEY_ATTRIBUTES QLatin1String("attributes")
#define KEY_INDICES QLatin1String("indices")
#define KEY_URI QLatin1String("uri")
#define KEY_FORMAT QLatin1String("format")
#define KEY_PASSES QLatin1String("passes")
#define KEY_SOURCE QLatin1String("source")
#define KEY_SAMPLER QLatin1String("sampler")
#define KEY_SAMPLERS QLatin1String("samplers")
#define KEY_SEMANTIC QLatin1String("semantic")
#define KEY_STATES QLatin1String("states")
#define KEY_UNIFORMS QLatin1String("uniforms")
#define KEY_PARAMETERS QLatin1String("parameters")
#define KEY_WRAP_S QLatin1String("wrapS")
#define KEY_MIN_FILTER QLatin1String("minFilter")
#define KEY_MAG_FILTER QLatin1String("magFilter")
#define KEY_LIGHT QLatin1String("light")
#define KEY_LIGHTS QLatin1String("lights")
#define KEY_POINT_LIGHT QLatin1String("point")
#define KEY_DIRECTIONAL_LIGHT QLatin1String("directional")
#define KEY_SPOT_LIGHT QLatin1String("spot")
#define KEY_AMBIENT_LIGHT QLatin1String("ambient")
#define KEY_COLOR QLatin1String("color")
#define KEY_FALLOFF_ANGLE QLatin1String("falloffAngle")
#define KEY_DIRECTION QLatin1String("direction")
#define KEY_CONST_ATTENUATION QLatin1String("constantAttenuation")
#define KEY_LINEAR_ATTENUATION QLatin1String("linearAttenuation")
#define KEY_QUAD_ATTENUATION QLatin1String("quadraticAttenuation")
#define KEY_INTENSITY QLatin1String("intensity")
#define KEY_PBR_METAL_ROUGH QLatin1String("pbrMetallicRoughness")
#define KEY_BASE_COLOR QLatin1String("baseColorFactor")
#define KEY_BASE_COLOR_TEX QLatin1String("baseColorTexture")
#define KEY_METAL_FACTOR QLatin1String("metallicFactor")
#define KEY_METAL_ROUGH_TEX QLatin1String("metallicRoughnessTexture")
#define KEY_ROUGH_FACTOR QLatin1String("roughnessFactor")
#define KEY_NORMAL_TEX QLatin1String("normalTexture")
#define KEY_OCCLUSION_TEX QLatin1String("occlusionTexture")
#define KEY_INDEX QLatin1String("index")
#define KEY_INSTANCE_TECHNIQUE QLatin1String("instanceTechnique")
#define KEY_INSTANCE_PROGRAM QLatin1String("instanceProgram")
#define KEY_BUFFER_VIEWS QLatin1String("bufferViews")
#define KEY_BUFFER_VIEW QLatin1String("bufferView")
#define KEY_VERTEX_SHADER QLatin1String("vertexShader")
#define KEY_FRAGMENT_SHADER QLatin1String("fragmentShader")
#define KEY_TESS_CTRL_SHADER QLatin1String("tessCtrlShader")
#define KEY_TESS_EVAL_SHADER QLatin1String("tessEvalShader")
#define KEY_GEOMETRY_SHADER QLatin1String("geometryShader")
#define KEY_COMPUTE_SHADER QLatin1String("computeShader")
#define KEY_INTERNAL_FORMAT QLatin1String("internalFormat")
#define KEY_COMPONENT_TYPE QLatin1String("componentType")
#define KEY_ASPECT_RATIO QLatin1String("aspect_ratio")
#define KEY_VALUE QLatin1String("value")
#define KEY_ENABLE QLatin1String("enable")
#define KEY_FUNCTIONS QLatin1String("functions")
#define KEY_BLEND_EQUATION QLatin1String("blendEquationSeparate")
#define KEY_BLEND_FUNCTION QLatin1String("blendFuncSeparate")
#define KEY_TECHNIQUE_CORE QLatin1String("techniqueCore")
#define KEY_TECHNIQUE_GL2 QLatin1String("techniqueGL2")
#define KEY_GABIFILTER QLatin1String("gapifilter")
#define KEY_API QLatin1String("api")
#define KEY_MAJORVERSION QLatin1String("majorVersion")
#define KEY_MINORVERSION QLatin1String("minorVersion")
#define KEY_PROFILE QLatin1String("profile")
#define KEY_VENDOR QLatin1String("vendor")
#define KEY_FILTERKEYS QLatin1String("filterkeys")
#define KEY_RENDERPASSES QLatin1String("renderpasses")
#define KEY_EFFECT QLatin1String("effect")
#define KEY_EFFECTS QLatin1String("effects")
#define KEY_PROPERTIES QLatin1String("properties")
#define KEY_POSITION QLatin1String("position")
#define KEY_UPVECTOR QLatin1String("upVector")
#define KEY_VIEW_CENTER QLatin1String("viewCenter")
QT_BEGIN_NAMESPACE
using namespace Qt3DCore;
using namespace Qt3DExtras;
namespace {
inline QVector3D jsonArrToVec3(const QJsonArray &array)
{
return QVector3D(array[0].toDouble(), array[1].toDouble(), array[2].toDouble());
}
inline QVector4D jsonArrToVec4(const QJsonArray &array)
{
return QVector4D(array[0].toDouble(), array[1].toDouble(),
array[2].toDouble(), array[3].toDouble());
}
inline QVariant jsonArrToColorVariant(const QJsonArray &array)
{
return QVariant(QColor::fromRgbF(array[0].toDouble(), array[1].toDouble(),
array[2].toDouble(), array[3].toDouble()));
}
inline QColor vec4ToQColor(const QVariant &vec4Var)
{
const QVector4D v = vec4Var.value<QVector4D>();
return QColor::fromRgbF(v.x(), v.y(), v.z());
}
inline QVariant vec4ToColorVariant(const QVariant &vec4Var)
{
return QVariant(vec4ToQColor(vec4Var));
}
Qt3DRender::QFilterKey *buildFilterKey(const QString &key, const QJsonValue &val)
{
Qt3DRender::QFilterKey *fk = new Qt3DRender::QFilterKey;
fk->setName(key);
if (val.isString())
fk->setValue(val.toString());
else
fk->setValue(val.toInt());
return fk;
}
} // namespace
namespace Qt3DRender {
Q_LOGGING_CATEGORY(GLTFImporterLog, "Qt3D.GLTFImport", QtWarningMsg);
class GLTFRawTextureImage : public QAbstractTextureImage
{
Q_OBJECT
public:
explicit GLTFRawTextureImage(QNode *parent = nullptr);
QTextureImageDataGeneratorPtr dataGenerator() const final;
void setImage(const QImage &image);
private:
QImage m_image;
class GLTFRawTextureImageFunctor : public QTextureImageDataGenerator
{
public:
explicit GLTFRawTextureImageFunctor(const QImage &image);
QTextureImageDataPtr operator()() final;
bool operator ==(const QTextureImageDataGenerator &other) const final;
QT3D_FUNCTOR(GLTFRawTextureImageFunctor)
private:
QImage m_image;
};
};
GLTFImporter::GLTFImporter()
: QSceneImporter()
, m_parseDone(false)
, m_majorVersion(1)
, m_minorVersion(0)
{
}
GLTFImporter::~GLTFImporter()
{
}
/*!
\class Qt3DRender::GLTFImporter
\inmodule Qt3DRender
\internal
\brief Handles importing of gltf files.
*/
/*!
Set the base \a path for importing scenes.
*/
void GLTFImporter::setBasePath(const QString& path)
{
m_basePath = path;
}
/*!
Set a \a json document as the file used for importing a scene.
Returns true if the operation is successful.
*/
bool GLTFImporter::setJSON(const QJsonDocument &json)
{
if (!json.isObject()) {
return false;
}
m_json = json;
m_parseDone = false;
return true;
}
/*!
* Sets the path based on parameter \a source. The path is
* used by the parser to load the scene file.
* If the file is valid, parsing is automatically triggered.
*/
void GLTFImporter::setSource(const QUrl &source)
{
const QString path = QUrlHelper::urlToLocalFileOrQrc(source);
QFileInfo finfo(path);
if (Q_UNLIKELY(!finfo.exists())) {
qCWarning(GLTFImporterLog, "missing file: %ls", qUtf16PrintableImpl(path));
return;
}
QFile f(path);
f.open(QIODevice::ReadOnly);
if (Q_UNLIKELY(!setJSON(qLoadGLTF(f.readAll())))) {
qCWarning(GLTFImporterLog, "not a JSON document");
return;
}
setBasePath(finfo.dir().absolutePath());
}
/*!
* Sets the \a basePath used by the parser to load the scene file.
* If the file derived from \a data is valid, parsing is automatically
* triggered.
*/
void GLTFImporter::setData(const QByteArray& data, const QString &basePath)
{
if (Q_UNLIKELY(!setJSON(qLoadGLTF(data)))) {
qCWarning(GLTFImporterLog, "not a JSON document");
return;
}
setBasePath(basePath);
}
/*!
* Returns true if the \a extensions are supported by the
* GLTF parser.
*/
bool GLTFImporter::areFileTypesSupported(const QStringList &extensions) const
{
return GLTFImporter::isGLTFSupported(extensions);
}
/*!
Imports the node specified in \a id from the GLTF file.
*/
Qt3DCore::QEntity* GLTFImporter::node(const QString &id)
{
QJsonValue jsonVal;
if (m_majorVersion > 1) {
const QJsonArray nodes = m_json.object().value(KEY_NODES).toArray();
if (Q_UNLIKELY(id.toInt() >= nodes.count())) {
qCWarning(GLTFImporterLog, "unknown node %ls in GLTF file %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
return nullptr;
}
jsonVal = nodes[id.toInt()];
} else {
const QJsonObject nodes = m_json.object().value(KEY_NODES).toObject();
jsonVal = nodes.value(id);
if (Q_UNLIKELY(jsonVal.isUndefined())) {
qCWarning(GLTFImporterLog, "unknown node %ls in GLTF file %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
return nullptr;
}
}
const QJsonObject jsonObj = jsonVal.toObject();
QEntity* result = nullptr;
// Qt3D has a limitation that a QEntity can only have 1 mesh and 1 material component
// So if the node has only 1 mesh, we only create 1 QEntity
// Otherwise if there are n meshes, there is 1 QEntity, with n children for each mesh/material combo
{
QVector<QEntity *> entities;
const QJsonValue meshesValue = jsonObj.value(KEY_MESHES);
if (meshesValue.isUndefined()) {
const QJsonValue mesh = jsonObj.value(KEY_MESH);
if (!mesh.isUndefined()) {
const QString meshName = QString::number(mesh.toInt());
const auto geometryRenderers = qAsConst(m_meshDict).equal_range(meshName);
for (auto it = geometryRenderers.first; it != geometryRenderers.second; ++it) {
QGeometryRenderer *geometryRenderer = it.value();
QEntity *entity = new QEntity;
entity->addComponent(geometryRenderer);
QMaterial *mat = material(m_meshMaterialDict[geometryRenderer]);
if (mat)
entity->addComponent(mat);
entities.append(entity);
}
}
} else {
const auto meshes = meshesValue.toArray();
for (const QJsonValue &mesh : meshes) {
const QString meshName = mesh.toString();
const auto geometryRenderers = qAsConst(m_meshDict).equal_range(meshName);
if (Q_UNLIKELY(geometryRenderers.first == geometryRenderers.second)) {
qCWarning(GLTFImporterLog, "node %ls references unknown mesh %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(meshName));
continue;
}
for (auto it = geometryRenderers.first; it != geometryRenderers.second; ++it) {
QGeometryRenderer *geometryRenderer = it.value();
QEntity *entity = new QEntity;
entity->addComponent(geometryRenderer);
QMaterial *mat = material(m_meshMaterialDict[geometryRenderer]);
if (mat)
entity->addComponent(mat);
entities.append(entity);
}
}
}
switch (entities.size()) {
case 0:
break;
case 1:
result = qAsConst(entities).first();
break;
default:
result = new QEntity;
for (QEntity *entity : qAsConst(entities))
entity->setParent(result);
}
}
const auto cameraVal = jsonObj.value(KEY_CAMERA);
const auto matrix = jsonObj.value(KEY_MATRIX);
const auto rotation = jsonObj.value(KEY_ROTATION);
const auto translation = jsonObj.value(KEY_TRANSLATION);
const auto scale = jsonObj.value(KEY_SCALE);
Qt3DCore::QTransform *trans = nullptr;
QCameraLens *cameraLens = nullptr;
QCamera *cameraEntity = nullptr;
// If the node contains no meshes, results will still be null here.
// If the node has camera and transform, promote it to QCamera, as that makes it more
// convenient to adjust the imported camera in the application.
if (result == nullptr) {
if (!cameraVal.isUndefined()
&& (!matrix.isUndefined() || !rotation.isUndefined() || !translation.isUndefined()
|| !scale.isUndefined())) {
cameraEntity = new QCamera;
trans = cameraEntity->transform();
cameraLens = cameraEntity->lens();
result = cameraEntity;
} else {
result = new QEntity;
}
}
// recursively retrieve children
const auto children = jsonObj.value(KEY_CHILDREN).toArray();
for (const QJsonValue &c : children) {
QEntity* child = node((m_majorVersion > 1) ? QString::number(c.toInt()) : c.toString());
if (!child)
continue;
child->setParent(result);
}
renameFromJson(jsonObj, result);
// Node Transforms
if (!matrix.isUndefined()) {
QMatrix4x4 m(Qt::Uninitialized);
QJsonArray matrixValues = matrix.toArray();
for (int i = 0; i < 16; ++i) {
double v = matrixValues.at(i).toDouble();
m(i % 4, i >> 2) = v;
}
if (!trans)
trans = new Qt3DCore::QTransform;
trans->setMatrix(m);
}
// Rotation quaternion
if (!rotation.isUndefined()) {
if (!trans)
trans = new Qt3DCore::QTransform;
QQuaternion quaternion(jsonArrToVec4(rotation.toArray()));
trans->setRotation(quaternion);
}
// Translation
if (!translation.isUndefined()) {
if (!trans)
trans = new Qt3DCore::QTransform;
trans->setTranslation(jsonArrToVec3(translation.toArray()));
}
// Scale
if (!scale.isUndefined()) {
if (!trans)
trans = new Qt3DCore::QTransform;
trans->setScale3D(jsonArrToVec3(scale.toArray()));
}
// Add the Transform component
if (trans != nullptr)
result->addComponent(trans);
if (!cameraVal.isUndefined()) {
const bool newLens = cameraLens == nullptr;
if (newLens)
cameraLens = new QCameraLens;
const QString cameraID = (m_majorVersion > 1) ? QString::number(cameraVal.toInt()) : cameraVal.toString();
if (!fillCamera(*cameraLens, cameraEntity, cameraID)) {
qCWarning(GLTFImporterLog, "failed to build camera: %ls on node %ls",
qUtf16PrintableImpl(cameraID), qUtf16PrintableImpl(id));
} else if (newLens) {
result->addComponent(cameraLens);
}
}
const auto extensionsVal = jsonObj.value(KEY_EXTENSIONS);
if (!extensionsVal.isUndefined()) {
const auto commonMat = extensionsVal.toObject().value(KEY_COMMON_MAT);
if (!commonMat.isUndefined()) {
const QJsonValue lightVal = commonMat.toObject().value(KEY_LIGHT);
const QString lightId = (m_majorVersion > 1) ? QString::number(lightVal.toInt()) : lightVal.toString();
QAbstractLight *lightComp = m_lights.value(lightId);
if (Q_UNLIKELY(!lightComp)) {
qCWarning(GLTFImporterLog, "failed to find light: %ls for node %ls",
qUtf16PrintableImpl(lightId), qUtf16PrintableImpl(id));
} else {
result->addComponent(lightComp);
}
}
}
return result;
}
/*!
Imports the scene specified in parameter \a id.
*/
Qt3DCore::QEntity* GLTFImporter::scene(const QString &id)
{
parse();
QEntity* sceneEntity = nullptr;
if (m_majorVersion > 1) {
const QJsonArray scenes = m_json.object().value(KEY_SCENES).toArray();
const auto sceneVal = scenes.first();
if (Q_UNLIKELY(sceneVal.isUndefined())) {
if (Q_UNLIKELY(!id.isNull()))
qCWarning(GLTFImporterLog, "GLTF: no such scene %ls in file %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
return defaultScene();
}
const QJsonObject sceneObj = sceneVal.toObject();
sceneEntity = new QEntity;
const auto nodes = sceneObj.value(KEY_NODES).toArray();
for (const QJsonValue &n : nodes) {
QEntity* child = node(QString::number(n.toInt()));
if (!child)
continue;
child->setParent(sceneEntity);
}
} else {
const QJsonObject scenes = m_json.object().value(KEY_SCENES).toObject();
const auto sceneVal = scenes.value(id);
if (Q_UNLIKELY(sceneVal.isUndefined())) {
if (Q_UNLIKELY(!id.isNull()))
qCWarning(GLTFImporterLog, "GLTF: no such scene %ls in file %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
return defaultScene();
}
const QJsonObject sceneObj = sceneVal.toObject();
sceneEntity = new QEntity;
const auto nodes = sceneObj.value(KEY_NODES).toArray();
for (const QJsonValue &nnv : nodes) {
QString nodeName = nnv.toString();
QEntity* child = node(nodeName);
if (!child)
continue;
child->setParent(sceneEntity);
}
}
cleanup();
return sceneEntity;
}
GLTFImporter::BufferData::BufferData()
: length(0)
, data(nullptr)
{
}
GLTFImporter::BufferData::BufferData(const QJsonObject &json)
: length(json.value(KEY_BYTE_LENGTH).toInt()),
path(json.value(KEY_URI).toString()),
data(nullptr)
{
}
GLTFImporter::ParameterData::ParameterData() :
type(0)
{
}
GLTFImporter::ParameterData::ParameterData(const QJsonObject &json)
: semantic(json.value(KEY_SEMANTIC).toString()),
type(json.value(KEY_TYPE).toInt())
{
}
GLTFImporter::AccessorData::AccessorData()
: type(QAttribute::Float)
, dataSize(0)
, count(0)
, offset(0)
, stride(0)
{
}
GLTFImporter::AccessorData::AccessorData(const QJsonObject &json, int major, int minor)
: type(accessorTypeFromJSON(json.value(KEY_COMPONENT_TYPE).toInt())),
dataSize(accessorDataSizeFromJson(json.value(KEY_TYPE).toString())),
count(json.value(KEY_COUNT).toInt()),
offset(0),
stride(0)
{
Q_UNUSED(minor)
if (major > 1) {
bufferViewName = QString::number(json.value(KEY_BUFFER_VIEW).toInt());
} else {
bufferViewName = json.value(KEY_BUFFER_VIEW).toString();
}
const auto byteOffset = json.value(KEY_BYTE_OFFSET);
if (!byteOffset.isUndefined())
offset = byteOffset.toInt();
const auto byteStride = json.value(KEY_BYTE_STRIDE);
if (!byteStride.isUndefined())
stride = byteStride.toInt();
}
bool GLTFImporter::isGLTFSupported(const QStringList &extensions)
{
for (auto suffix: qAsConst(extensions)) {
suffix = suffix.toLower();
if (suffix == QLatin1String("json") || suffix == QLatin1String("gltf") || suffix == QLatin1String("qgltf"))
return true;
}
return false;
}
bool GLTFImporter::isEmbeddedResource(const QString &url)
{
return url.startsWith("data:");
}
void GLTFImporter::renameFromJson(const QJsonObject &json, QObject * const object)
{
const auto name = json.value(KEY_NAME);
if (!name.isUndefined())
object->setObjectName(name.toString());
}
bool GLTFImporter::hasStandardUniformNameFromSemantic(const QString &semantic)
{
//Standard Uniforms
if (semantic.isEmpty())
return false;
switch (semantic.at(0).toLatin1()) {
case 'L':
// return semantic == QLatin1String("LOCAL");
return false;
case 'M':
return semantic == QLatin1String("MODEL")
|| semantic == QLatin1String("MODELVIEW")
|| semantic == QLatin1String("MODELVIEWPROJECTION")
|| semantic == QLatin1String("MODELINVERSE")
|| semantic == QLatin1String("MODELVIEWPROJECTIONINVERSE")
|| semantic == QLatin1String("MODELINVERSETRANSPOSE")
|| semantic == QLatin1String("MODELVIEWINVERSETRANSPOSE");
case 'V':
return semantic == QLatin1String("VIEW")
|| semantic == QLatin1String("VIEWINVERSE")
|| semantic == QLatin1String("VIEWPORT");
case 'P':
return semantic == QLatin1String("PROJECTION")
|| semantic == QLatin1String("PROJECTIONINVERSE");
}
return false;
}
QString GLTFImporter::standardAttributeNameFromSemantic(const QString &semantic)
{
//Standard Attributes
if (semantic.startsWith(QLatin1String("POSITION")))
return QAttribute::defaultPositionAttributeName();
if (semantic.startsWith(QLatin1String("NORMAL")))
return QAttribute::defaultNormalAttributeName();
if (semantic.startsWith(QLatin1String("TEXCOORD")))
return QAttribute::defaultTextureCoordinateAttributeName();
if (semantic.startsWith(QLatin1String("COLOR")))
return QAttribute::defaultColorAttributeName();
if (semantic.startsWith(QLatin1String("TANGENT")))
return QAttribute::defaultTangentAttributeName();
// if (semantic.startsWith(QLatin1String("JOINT")));
// if (semantic.startsWith(QLatin1String("JOINTMATRIX")));
// if (semantic.startsWith(QLatin1String("WEIGHT")));
return QString();
}
QParameter *GLTFImporter::parameterFromTechnique(QTechnique *technique,
const QString &parameterName)
{
const QList<QParameter *> parameters = m_techniqueParameters.value(technique);
for (QParameter *parameter : parameters) {
if (parameter->name() == parameterName)
return parameter;
}
return nullptr;
}
Qt3DCore::QEntity* GLTFImporter::defaultScene()
{
if (Q_UNLIKELY(m_defaultScene.isEmpty())) {
qCWarning(GLTFImporterLog, "no default scene");
return nullptr;
}
return scene(m_defaultScene);
}
QMaterial *GLTFImporter::materialWithCustomShader(const QString &id, const QJsonObject &jsonObj)
{
QString effectName = jsonObj.value(KEY_EFFECT).toString();
if (effectName.isEmpty()) {
// GLTF custom shader material (with qgltf tool specific customizations)
// Default ES2 Technique
QString techniqueName = jsonObj.value(KEY_TECHNIQUE).toString();
const auto it = qAsConst(m_techniques).find(techniqueName);
if (Q_UNLIKELY(it == m_techniques.cend())) {
qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls",
qUtf16PrintableImpl(techniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
return nullptr;
}
QTechnique *technique = *it;
technique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGLES);
technique->graphicsApiFilter()->setMajorVersion(2);
technique->graphicsApiFilter()->setMinorVersion(0);
technique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::NoProfile);
//Optional Core technique
QTechnique *coreTechnique = nullptr;
QTechnique *gl2Technique = nullptr;
QString coreTechniqueName = jsonObj.value(KEY_TECHNIQUE_CORE).toString();
if (!coreTechniqueName.isNull()) {
const auto it = qAsConst(m_techniques).find(coreTechniqueName);
if (Q_UNLIKELY(it == m_techniques.cend())) {
qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls",
qUtf16PrintableImpl(coreTechniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
} else {
coreTechnique = it.value();
coreTechnique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGL);
coreTechnique->graphicsApiFilter()->setMajorVersion(3);
coreTechnique->graphicsApiFilter()->setMinorVersion(1);
coreTechnique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::CoreProfile);
}
}
//Optional GL2 technique
QString gl2TechniqueName = jsonObj.value(KEY_TECHNIQUE_GL2).toString();
if (!gl2TechniqueName.isNull()) {
const auto it = qAsConst(m_techniques).find(gl2TechniqueName);
if (Q_UNLIKELY(it == m_techniques.cend())) {
qCWarning(GLTFImporterLog, "unknown technique %ls for material %ls in GLTF file %ls",
qUtf16PrintableImpl(gl2TechniqueName), qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
} else {
gl2Technique = it.value();
gl2Technique->graphicsApiFilter()->setApi(QGraphicsApiFilter::OpenGL);
gl2Technique->graphicsApiFilter()->setMajorVersion(2);
gl2Technique->graphicsApiFilter()->setMinorVersion(0);
gl2Technique->graphicsApiFilter()->setProfile(QGraphicsApiFilter::NoProfile);
}
}
// glTF doesn't deal in effects, but we need a trivial one to wrap
// up our techniques
// However we need to create a unique effect for each material instead
// of caching because QMaterial does not keep up with effects
// its not the parent of.
QEffect *effect = new QEffect;
effect->setObjectName(techniqueName);
effect->addTechnique(technique);
if (coreTechnique != nullptr)
effect->addTechnique(coreTechnique);
if (gl2Technique != nullptr)
effect->addTechnique(gl2Technique);
QMaterial *mat = new QMaterial;
mat->setEffect(effect);
renameFromJson(jsonObj, mat);
const QJsonObject values = jsonObj.value(KEY_VALUES).toObject();
for (auto it = values.begin(), end = values.end(); it != end; ++it) {
const QString vName = it.key();
QParameter *param = parameterFromTechnique(technique, vName);
if (param == nullptr && coreTechnique != nullptr)
param = parameterFromTechnique(coreTechnique, vName);
if (param == nullptr && gl2Technique != nullptr)
param = parameterFromTechnique(gl2Technique, vName);
if (Q_UNLIKELY(!param)) {
qCWarning(GLTFImporterLog, "unknown parameter: %ls in technique %ls processing material %ls",
qUtf16PrintableImpl(vName), qUtf16PrintableImpl(techniqueName), qUtf16PrintableImpl(id));
continue;
}
ParameterData paramData = m_parameterDataDict.value(param);
QVariant var = parameterValueFromJSON(paramData.type, it.value());
mat->addParameter(new QParameter(param->name(), var));
} // of material technique-instance values iteration
return mat;
} else {
// Qt3D exported QGLTF custom material
QMaterial *mat = new QMaterial;
renameFromJson(jsonObj, mat);
QEffect *effect = m_effects.value(effectName);
if (effect) {
mat->setEffect(effect);
} else {
qCWarning(GLTFImporterLog, "Effect %ls missing for material %ls",
qUtf16PrintableImpl(effectName), qUtf16PrintableImpl(mat->objectName()));
}
const QJsonObject params = jsonObj.value(KEY_PARAMETERS).toObject();
for (auto it = params.begin(), end = params.end(); it != end; ++it)
mat->addParameter(buildParameter(it.key(), it.value().toObject()));
return mat;
}
}
QMaterial *GLTFImporter::commonMaterial(const QJsonObject &jsonObj)
{
const auto jsonExt =
jsonObj.value(KEY_EXTENSIONS).toObject().value(KEY_COMMON_MAT).toObject();
if (m_majorVersion == 1 && jsonExt.isEmpty())
return nullptr;
QVariantHash params;
bool hasDiffuseMap = false;
bool hasSpecularMap = false;
bool hasNormalMap = false;
bool hasAlpha = false;
if (m_majorVersion > 1) {
QMaterial *mat = pbrMaterial(jsonObj);
if (mat)
return mat;
}
const QJsonObject values = jsonExt.value(KEY_VALUES).toObject();
for (auto it = values.begin(), end = values.end(); it != end; ++it) {
const QString vName = it.key();
const QJsonValue val = it.value();
QVariant var;
QString propertyName = vName;
if (vName == QLatin1String("ambient") && val.isArray()) {
var = vec4ToColorVariant(parameterValueFromJSON(GL_FLOAT_VEC4, val));
} else if (vName == QLatin1String("diffuse")) {
if (val.isString()) {
var = parameterValueFromJSON(GL_SAMPLER_2D, val);
hasDiffuseMap = true;
} else if (val.isArray()) {
var = vec4ToColorVariant(parameterValueFromJSON(GL_FLOAT_VEC4, val));
}
} else if (vName == QLatin1String("specular")) {
if (val.isString()) {
var = parameterValueFromJSON(GL_SAMPLER_2D, val);
hasSpecularMap = true;
} else if (val.isArray()) {
var = vec4ToColorVariant(parameterValueFromJSON(GL_FLOAT_VEC4, val));
}
} else if (vName == QLatin1String("cool")) { // Custom Qt3D extension for gooch
var = vec4ToColorVariant(parameterValueFromJSON(GL_FLOAT_VEC4, val));
} else if (vName == QLatin1String("warm")) { // Custom Qt3D extension for gooch
var = vec4ToColorVariant(parameterValueFromJSON(GL_FLOAT_VEC4, val));
} else if (vName == QLatin1String("shininess") && val.isDouble()) {
var = parameterValueFromJSON(GL_FLOAT, val);
} else if (vName == QLatin1String("normalmap") && val.isString()) {
var = parameterValueFromJSON(GL_SAMPLER_2D, val);
propertyName = QStringLiteral("normal");
hasNormalMap = true;
} else if (vName == QLatin1String("transparency")) {
var = parameterValueFromJSON(GL_FLOAT, val);
propertyName = QStringLiteral("alpha");
hasAlpha = true;
} else if (vName == QLatin1String("transparent")) {
hasAlpha = parameterValueFromJSON(GL_BOOL, val).toBool();
} else if (vName == QLatin1String("textureScale")) {
var = parameterValueFromJSON(GL_FLOAT, val);
propertyName = QStringLiteral("textureScale");
} else if (vName == QLatin1String("alpha")) { // Custom Qt3D extension for gooch
var = parameterValueFromJSON(GL_FLOAT, val);
} else if (vName == QLatin1String("beta")) { // Custom Qt3D extension for gooch
var = parameterValueFromJSON(GL_FLOAT, val);
}
if (var.isValid())
params[propertyName] = var;
}
const QJsonObject funcValues = jsonExt.value(KEY_FUNCTIONS).toObject();
if (!funcValues.isEmpty()) {
const QJsonArray fArray = funcValues[KEY_BLEND_FUNCTION].toArray();
const QJsonArray eArray = funcValues[KEY_BLEND_EQUATION].toArray();
if (fArray.size() == 4) {
params[QStringLiteral("sourceRgbArg")] = fArray[0].toInt();
params[QStringLiteral("sourceAlphaArg")] = fArray[1].toInt();
params[QStringLiteral("destinationRgbArg")] = fArray[2].toInt();
params[QStringLiteral("destinationAlphaArg")] = fArray[3].toInt();
}
// We get separate values but our QPhongAlphaMaterial only supports single argument,
// so we just use the first one.
if (eArray.size() == 2)
params[QStringLiteral("blendFunctionArg")] = eArray[0].toInt();
}
QMaterial *mat = nullptr;
const QString technique = jsonExt.value(KEY_TECHNIQUE).toString();
if (technique == QStringLiteral("PHONG")) {
if (hasNormalMap) {
if (hasSpecularMap) {
mat = new QNormalDiffuseSpecularMapMaterial;
} else {
if (Q_UNLIKELY(!hasDiffuseMap)) {
qCWarning(GLTFImporterLog, "Common material with normal and specular maps needs a diffuse map as well");
} else {
if (hasAlpha)
mat = new QNormalDiffuseMapAlphaMaterial;
else
mat = new QNormalDiffuseMapMaterial;
}
}
} else {
if (hasSpecularMap) {
if (Q_UNLIKELY(!hasDiffuseMap))
qCWarning(GLTFImporterLog, "Common material with specular map needs a diffuse map as well");
else
mat = new QDiffuseSpecularMapMaterial;
} else if (hasDiffuseMap) {
mat = new QDiffuseMapMaterial;
} else {
if (hasAlpha)
mat = new QPhongAlphaMaterial;
else
mat = new QPhongMaterial;
}
}
} else if (technique == QStringLiteral("GOOCH")) { // Qt3D specific extension
mat = new QGoochMaterial;
} else if (technique == QStringLiteral("PERVERTEX")) { // Qt3D specific extension
mat = new QPerVertexColorMaterial;
}
if (Q_UNLIKELY(!mat)) {
qCWarning(GLTFImporterLog, "Could not find a suitable built-in material for KHR_materials_common");
} else {
for (QVariantHash::const_iterator it = params.constBegin(), itEnd = params.constEnd(); it != itEnd; ++it)
mat->setProperty(it.key().toUtf8(), it.value());
}
renameFromJson(jsonObj, mat);
return mat;
}
QMaterial *GLTFImporter::pbrMaterial(const QJsonObject &jsonObj)
{
// check for pbrMetallicRoughness material
QMetalRoughMaterial *mrMaterial = nullptr;
QJsonValue jsonValue = jsonObj.value(KEY_PBR_METAL_ROUGH);
if (!jsonValue.isUndefined()) {
const QJsonObject pbrObj = jsonValue.toObject();
mrMaterial = new QMetalRoughMaterial;
jsonValue = pbrObj.value(KEY_BASE_COLOR);
if (!jsonValue.isUndefined())
mrMaterial->setBaseColor(jsonArrToColorVariant(jsonValue.toArray()));
jsonValue = pbrObj.value(KEY_BASE_COLOR_TEX);
if (!jsonValue.isUndefined()) {
const QJsonObject texObj = jsonValue.toObject();
const QString textureId = QString::number(texObj.value(KEY_INDEX).toInt());
const auto it = m_textures.find(textureId);
if (Q_UNLIKELY(it == m_textures.end())) {
qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId));
} else {
mrMaterial->setBaseColor(QVariant::fromValue(it.value()));
}
}
jsonValue = pbrObj.value(KEY_METAL_FACTOR);
if (!jsonValue.isUndefined())
mrMaterial->setMetalness(jsonValue.toVariant());
jsonValue = pbrObj.value(KEY_METAL_ROUGH_TEX);
if (!jsonValue.isUndefined()) {
const QJsonObject texObj = jsonValue.toObject();
const QString textureId = QString::number(texObj.value(KEY_INDEX).toInt());
const auto it = m_textures.find(textureId);
if (Q_UNLIKELY(it == m_textures.end())) {
qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId));
} else {
// find the texture again
const QJsonArray texArray = m_json.object().value(KEY_TEXTURES).toArray();
const QJsonObject tObj = texArray.at(texObj.value(KEY_INDEX).toInt()).toObject();
const QString sourceId = QString::number(tObj.value(KEY_SOURCE).toInt());
QImage image;
if (m_imagePaths.contains(sourceId)) {
image.load(m_imagePaths.value(sourceId));
} else if (m_imageData.contains(sourceId)) {
image = m_imageData.value(sourceId);
} else {
return mrMaterial;
}
// at this point, in image there is data for metalness (on B) and
// roughness (on G) bytes. 2 new textures are created
// to make Qt3D happy, since it samples only on R.
QTexture2D* metalTex = new QTexture2D;
QTexture2D* roughTex = new QTexture2D;
GLTFRawTextureImage* metalImgTex = new GLTFRawTextureImage();
GLTFRawTextureImage* roughImgTex = new GLTFRawTextureImage();
QImage metalness(image.size(), image.format());
QImage roughness(image.size(), image.format());
const uchar *imgData = image.constBits();
const int pixelBytes = image.depth() / 8;
Q_ASSERT_X(pixelBytes < 3, "GLTFImporter::pbrMaterial", "Unsupported texture format");
for (int y = 0; y < image.height(); y++) {
for (int x = 0; x < image.width(); x++) {
metalness.setPixel(x, y, qRgb(imgData[0], imgData[0], imgData[0]));
roughness.setPixel(x, y, qRgb(imgData[1], imgData[1], imgData[1]));
imgData += pixelBytes;
}
}
metalImgTex->setImage(metalness);
metalTex->addTextureImage(metalImgTex);
roughImgTex->setImage(roughness);
roughTex->addTextureImage(roughImgTex);
setTextureSamplerInfo("", tObj, metalTex);
setTextureSamplerInfo("", tObj, roughTex);
mrMaterial->setMetalness(QVariant::fromValue(metalTex));
mrMaterial->setRoughness(QVariant::fromValue(roughTex));
}
}
jsonValue = pbrObj.value(KEY_ROUGH_FACTOR);
if (!jsonValue.isUndefined())
mrMaterial->setRoughness(jsonValue.toVariant());
}
jsonValue = jsonObj.value(KEY_NORMAL_TEX);
if (!jsonValue.isUndefined()) {
const QJsonObject texObj = jsonValue.toObject();
const QString textureId = QString::number(texObj.value(KEY_INDEX).toInt());
const auto it = m_textures.find(textureId);
if (Q_UNLIKELY(it == m_textures.end())) {
qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId));
} else {
if (mrMaterial)
mrMaterial->setNormal(QVariant::fromValue(it.value()));
}
}
jsonValue = jsonObj.value(KEY_OCCLUSION_TEX);
if (!jsonValue.isUndefined()) {
const QJsonObject texObj = jsonValue.toObject();
const QString textureId = QString::number(texObj.value(KEY_INDEX).toInt());
const auto it = m_textures.find(textureId);
if (Q_UNLIKELY(it == m_textures.end())) {
qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId));
} else {
if (mrMaterial)
mrMaterial->setAmbientOcclusion(QVariant::fromValue(it.value()));
}
}
return mrMaterial;
}
QMaterial* GLTFImporter::material(const QString &id)
{
const auto it = qAsConst(m_materialCache).find(id);
if (it != m_materialCache.cend())
return it.value();
QJsonValue jsonVal;
if (m_majorVersion > 1) {
const QJsonArray mats = m_json.object().value(KEY_MATERIALS).toArray();
jsonVal = mats.at(id.toInt());
} else {
const QJsonObject mats = m_json.object().value(KEY_MATERIALS).toObject();
jsonVal = mats.value(id);
}
if (Q_UNLIKELY(jsonVal.isUndefined())) {
qCWarning(GLTFImporterLog, "unknown material %ls in GLTF file %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
return nullptr;
}
const QJsonObject jsonObj = jsonVal.toObject();
QMaterial *mat = nullptr;
// Prefer common materials over custom shaders.
mat = commonMaterial(jsonObj);
if (!mat)
mat = materialWithCustomShader(id, jsonObj);
m_materialCache[id] = mat;
return mat;
}
bool GLTFImporter::fillCamera(QCameraLens &lens, QCamera *cameraEntity, const QString &id) const
{
QJsonObject jsonObj;
if (m_majorVersion > 1) {
const QJsonArray camArray = m_json.object().value(KEY_CAMERAS).toArray();
if (id.toInt() >= camArray.count()) {
qCWarning(GLTFImporterLog, "unknown camera %ls in GLTF file %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
return false;
}
jsonObj = camArray[id.toInt()].toObject();
} else {
const auto jsonVal = m_json.object().value(KEY_CAMERAS).toObject().value(id);
if (Q_UNLIKELY(jsonVal.isUndefined())) {
qCWarning(GLTFImporterLog, "unknown camera %ls in GLTF file %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(m_basePath));
return false;
}
jsonObj = jsonVal.toObject();
}
QString camTy = jsonObj.value(KEY_TYPE).toString();
if (camTy == QLatin1String("perspective")) {
const auto pVal = jsonObj.value(KEY_PERSPECTIVE);
if (Q_UNLIKELY(pVal.isUndefined())) {
qCWarning(GLTFImporterLog, "camera: %ls missing 'perspective' object",
qUtf16PrintableImpl(id));
return false;
}
const QJsonObject pObj = pVal.toObject();
double aspectRatio = pObj.value(KEY_ASPECT_RATIO).toDouble();
double yfov = pObj.value(KEY_YFOV).toDouble();
double frustumNear = pObj.value(KEY_ZNEAR).toDouble();
double frustumFar = pObj.value(KEY_ZFAR).toDouble();
lens.setPerspectiveProjection(qRadiansToDegrees(yfov), aspectRatio, frustumNear,
frustumFar);
} else if (camTy == QLatin1String("orthographic")) {
const auto pVal = jsonObj.value(KEY_ORTHOGRAPHIC);
if (Q_UNLIKELY(pVal.isUndefined())) {
qCWarning(GLTFImporterLog, "camera: %ls missing 'orthographic' object",
qUtf16PrintableImpl(id));
return false;
}
const QJsonObject pObj = pVal.toObject();
double xmag = pObj.value(KEY_XMAG).toDouble() / 2.0f;
double ymag = pObj.value(KEY_YMAG).toDouble() / 2.0f;
double frustumNear = pObj.value(KEY_ZNEAR).toDouble();
double frustumFar = pObj.value(KEY_ZFAR).toDouble();
lens.setOrthographicProjection(-xmag, xmag, -ymag, ymag, frustumNear, frustumFar);
} else {
qCWarning(GLTFImporterLog, "camera: %ls has unsupported type: %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(camTy));
return false;
}
if (cameraEntity) {
if (jsonObj.contains(KEY_POSITION))
cameraEntity->setPosition(jsonArrToVec3(jsonObj.value(KEY_POSITION).toArray()));
if (jsonObj.contains(KEY_UPVECTOR))
cameraEntity->setUpVector(jsonArrToVec3(jsonObj.value(KEY_UPVECTOR).toArray()));
if (jsonObj.contains(KEY_VIEW_CENTER))
cameraEntity->setViewCenter(jsonArrToVec3(jsonObj.value(KEY_VIEW_CENTER).toArray()));
}
renameFromJson(jsonObj, &lens);
return true;
}
void GLTFImporter::parse()
{
if (m_parseDone)
return;
const QJsonValue asset = m_json.object().value(KEY_ASSET);
if (!asset.isUndefined())
processJSONAsset(asset.toObject());
if (m_majorVersion > 1) {
parseV2();
} else {
parseV1();
}
m_parseDone = true;
}
void GLTFImporter::parseV1()
{
const QJsonObject buffers = m_json.object().value(KEY_BUFFERS).toObject();
for (auto it = buffers.begin(), end = buffers.end(); it != end; ++it)
processJSONBuffer(it.key(), it.value().toObject());
const QJsonObject views = m_json.object().value(KEY_BUFFER_VIEWS).toObject();
loadBufferData();
for (auto it = views.begin(), end = views.end(); it != end; ++it)
processJSONBufferView(it.key(), it.value().toObject());
unloadBufferData();
const QJsonObject shaders = m_json.object().value(KEY_SHADERS).toObject();
for (auto it = shaders.begin(), end = shaders.end(); it != end; ++it)
processJSONShader(it.key(), it.value().toObject());
const QJsonObject programs = m_json.object().value(KEY_PROGRAMS).toObject();
for (auto it = programs.begin(), end = programs.end(); it != end; ++it)
processJSONProgram(it.key(), it.value().toObject());
const QJsonObject attrs = m_json.object().value(KEY_ACCESSORS).toObject();
for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it)
processJSONAccessor(it.key(), it.value().toObject());
const QJsonObject meshes = m_json.object().value(KEY_MESHES).toObject();
for (auto it = meshes.begin(), end = meshes.end(); it != end; ++it)
processJSONMesh(it.key(), it.value().toObject());
const QJsonObject images = m_json.object().value(KEY_IMAGES).toObject();
for (auto it = images.begin(), end = images.end(); it != end; ++it)
processJSONImage(it.key(), it.value().toObject());
const QJsonObject textures = m_json.object().value(KEY_TEXTURES).toObject();
for (auto it = textures.begin(), end = textures.end(); it != end; ++it)
processJSONTexture(it.key(), it.value().toObject());
const QJsonObject extensions = m_json.object().value(KEY_EXTENSIONS).toObject();
for (auto it = extensions.begin(), end = extensions.end(); it != end; ++it)
processJSONExtensions(it.key(), it.value().toObject());
const QJsonObject passes = m_json.object().value(KEY_RENDERPASSES).toObject();
for (auto it = passes.begin(), end = passes.end(); it != end; ++it)
processJSONRenderPass(it.key(), it.value().toObject());
const QJsonObject techniques = m_json.object().value(KEY_TECHNIQUES).toObject();
for (auto it = techniques.begin(), end = techniques.end(); it != end; ++it)
processJSONTechnique(it.key(), it.value().toObject());
const QJsonObject effects = m_json.object().value(KEY_EFFECTS).toObject();
for (auto it = effects.begin(), end = effects.end(); it != end; ++it)
processJSONEffect(it.key(), it.value().toObject());
m_defaultScene = m_json.object().value(KEY_SCENE).toString();
}
void GLTFImporter::parseV2()
{
int i;
const QJsonArray buffers = m_json.object().value(KEY_BUFFERS).toArray();
for (i = 0; i < buffers.count(); i++)
processJSONBuffer(QString::number(i), buffers[i].toObject());
const QJsonArray views = m_json.object().value(KEY_BUFFER_VIEWS).toArray();
loadBufferData();
for (i = 0; i < views.count(); i++)
processJSONBufferView(QString::number(i), views[i].toObject());
unloadBufferData();
const QJsonArray accessors = m_json.object().value(KEY_ACCESSORS).toArray();
for (i = 0; i < accessors.count(); i++)
processJSONAccessor(QString::number(i), accessors[i].toObject());
const QJsonArray meshes = m_json.object().value(KEY_MESHES).toArray();
for (i = 0; i < meshes.count(); i++)
processJSONMesh(QString::number(i), meshes[i].toObject());
const QJsonArray images = m_json.object().value(KEY_IMAGES).toArray();
for (i = 0; i < images.count(); i++)
processJSONImage(QString::number(i), images[i].toObject());
const QJsonArray textures = m_json.object().value(KEY_TEXTURES).toArray();
for (i = 0; i < textures.count(); i++)
processJSONTexture(QString::number(i), textures[i].toObject());
m_defaultScene = QString::number(m_json.object().value(KEY_SCENE).toInt());
}
namespace {
template <typename C>
void delete_if_without_parent(const C &c)
{
for (const auto *e : c) {
if (!e->parent())
delete e;
}
}
} // unnamed namespace
void GLTFImporter::cleanup()
{
m_meshDict.clear();
m_meshMaterialDict.clear();
m_accessorDict.clear();
delete_if_without_parent(m_materialCache);
m_materialCache.clear();
m_bufferDatas.clear();
m_buffers.clear();
m_shaderPaths.clear();
delete_if_without_parent(m_programs);
m_programs.clear();
for (const auto &params : qAsConst(m_techniqueParameters))
delete_if_without_parent(params);
m_techniqueParameters.clear();
delete_if_without_parent(m_techniques);
m_techniques.clear();
delete_if_without_parent(m_textures);
m_textures.clear();
m_imagePaths.clear();
m_imageData.clear();
m_defaultScene.clear();
m_parameterDataDict.clear();
delete_if_without_parent(m_renderPasses);
m_renderPasses.clear();
delete_if_without_parent(m_effects);
m_effects.clear();
}
void GLTFImporter::processJSONAsset(const QJsonObject &json)
{
const QString version = json.value(KEY_VERSION).toString();
if (!version.isEmpty()) {
const QStringList verTokens = version.split('.');
if (verTokens.length() >= 2) {
m_majorVersion = verTokens[0].toInt();
m_minorVersion = verTokens[1].toInt();
}
}
}
void GLTFImporter::processJSONBuffer(const QString &id, const QJsonObject& json)
{
// simply cache buffers for lookup by buffer-views
m_bufferDatas[id] = BufferData(json);
}
void GLTFImporter::processJSONBufferView(const QString &id, const QJsonObject& json)
{
QString bufName;
if (m_majorVersion > 1) {
bufName = QString::number(json.value(KEY_BUFFER).toInt());
} else {
bufName = json.value(KEY_BUFFER).toString();
}
const auto it = qAsConst(m_bufferDatas).find(bufName);
if (Q_UNLIKELY(it == m_bufferDatas.cend())) {
qCWarning(GLTFImporterLog, "unknown buffer: %ls processing view: %ls",
qUtf16PrintableImpl(bufName), qUtf16PrintableImpl(id));
return;
}
const auto &bufferData = *it;
const QJsonValue targetValue = json.value(KEY_TARGET);
int target;
if (targetValue.isUndefined()) {
target = GL_ARRAY_BUFFER;
} else {
target = targetValue.toInt();
}
quint64 offset = 0;
const auto byteOffset = json.value(KEY_BYTE_OFFSET);
if (!byteOffset.isUndefined()) {
offset = byteOffset.toInt();
qCDebug(GLTFImporterLog, "bv: %ls has offset: %lld", qUtf16PrintableImpl(id), offset);
}
quint64 len = json.value(KEY_BYTE_LENGTH).toInt();
QByteArray bytes = bufferData.data->mid(offset, len);
if (Q_UNLIKELY(bytes.count() != int(len))) {
qCWarning(GLTFImporterLog, "failed to read sufficient bytes from: %ls for view %ls",
qUtf16PrintableImpl(bufferData.path), qUtf16PrintableImpl(id));
}
Qt3DRender::QBuffer *b = new Qt3DRender::QBuffer();
b->setData(bytes);
m_buffers[id] = b;
}
void GLTFImporter::processJSONShader(const QString &id, const QJsonObject &jsonObject)
{
// shaders are trivial for the moment, defer the real work
// to the program section
QString path = jsonObject.value(KEY_URI).toString();
if (!isEmbeddedResource(path)) {
QFileInfo info(m_basePath, path);
if (Q_UNLIKELY(!info.exists())) {
qCWarning(GLTFImporterLog, "can't find shader %ls from path %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(path));
return;
}
m_shaderPaths[id] = info.absoluteFilePath();
} else {
const QByteArray base64Data = path.toLatin1().remove(0, path.indexOf(",") + 1);
m_shaderPaths[id] = QString(QByteArray::fromBase64(base64Data));
}
}
void GLTFImporter::processJSONProgram(const QString &id, const QJsonObject &jsonObject)
{
const QString fragName = jsonObject.value(KEY_FRAGMENT_SHADER).toString();
const QString vertName = jsonObject.value(KEY_VERTEX_SHADER).toString();
const auto fragIt = qAsConst(m_shaderPaths).find(fragName);
const auto vertIt = qAsConst(m_shaderPaths).find(vertName);
if (Q_UNLIKELY(fragIt == m_shaderPaths.cend() || vertIt == m_shaderPaths.cend())) {
qCWarning(GLTFImporterLog, "program: %ls missing shader: %ls %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(fragName), qUtf16PrintableImpl(vertName));
return;
}
QShaderProgram* prog = new QShaderProgram;
prog->setObjectName(id);
prog->setFragmentShaderCode(QShaderProgram::loadSource(QUrl::fromLocalFile(fragIt.value())));
prog->setVertexShaderCode(QShaderProgram::loadSource(QUrl::fromLocalFile(vertIt.value())));
const QString tessCtrlName = jsonObject.value(KEY_TESS_CTRL_SHADER).toString();
if (!tessCtrlName.isEmpty()) {
const auto it = qAsConst(m_shaderPaths).find(tessCtrlName);
prog->setTessellationControlShaderCode(
QShaderProgram::loadSource(QUrl::fromLocalFile(it.value())));
}
const QString tessEvalName = jsonObject.value(KEY_TESS_EVAL_SHADER).toString();
if (!tessEvalName.isEmpty()) {
const auto it = qAsConst(m_shaderPaths).find(tessEvalName);
prog->setTessellationEvaluationShaderCode(
QShaderProgram::loadSource(QUrl::fromLocalFile(it.value())));
}
const QString geomName = jsonObject.value(KEY_GEOMETRY_SHADER).toString();
if (!geomName.isEmpty()) {
const auto it = qAsConst(m_shaderPaths).find(geomName);
prog->setGeometryShaderCode(QShaderProgram::loadSource(QUrl::fromLocalFile(it.value())));
}
const QString computeName = jsonObject.value(KEY_COMPUTE_SHADER).toString();
if (!computeName.isEmpty()) {
const auto it = qAsConst(m_shaderPaths).find(computeName);
prog->setComputeShaderCode(QShaderProgram::loadSource(QUrl::fromLocalFile(it.value())));
}
m_programs[id] = prog;
}
void GLTFImporter::processJSONTechnique(const QString &id, const QJsonObject &jsonObject )
{
QTechnique *t = new QTechnique;
t->setObjectName(id);
const QJsonObject gabifilter = jsonObject.value(KEY_GABIFILTER).toObject();
if (gabifilter.isEmpty()) {
// Regular GLTF technique
// Parameters
QHash<QString, QParameter *> paramDict;
const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject();
for (auto it = params.begin(), end = params.end(); it != end; ++it) {
const QString pname = it.key();
const QJsonObject po = it.value().toObject();
QParameter *p = buildParameter(pname, po);
m_parameterDataDict.insert(p, ParameterData(po));
// We don't want to insert invalid parameters to techniques themselves, but we
// need to maintain link between all parameters and techniques so we can resolve
// parameter type later when creating materials.
if (p->value().isValid())
t->addParameter(p);
paramDict[pname] = p;
}
// Program
QRenderPass *pass = new QRenderPass;
addProgramToPass(pass, jsonObject.value(KEY_PROGRAM).toString());
// Attributes
const QJsonObject attrs = jsonObject.value(KEY_ATTRIBUTES).toObject();
for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) {
QString pname = it.value().toString();
QParameter *parameter = paramDict.value(pname, nullptr);
QString attributeName = pname;
if (Q_UNLIKELY(!parameter)) {
qCWarning(GLTFImporterLog, "attribute %ls defined in instanceProgram but not as parameter",
qUtf16PrintableImpl(pname));
continue;
}
//Check if the parameter has a standard attribute semantic
const auto paramDataIt = m_parameterDataDict.find(parameter);
QString standardAttributeName = standardAttributeNameFromSemantic(paramDataIt->semantic);
if (!standardAttributeName.isNull()) {
attributeName = standardAttributeName;
t->removeParameter(parameter);
m_parameterDataDict.erase(paramDataIt);
paramDict.remove(pname);
delete parameter;
}
} // of program-instance attributes
// Uniforms
const QJsonObject uniforms = jsonObject.value(KEY_UNIFORMS).toObject();
for (auto it = uniforms.begin(), end = uniforms.end(); it != end; ++it) {
const QString pname = it.value().toString();
QParameter *parameter = paramDict.value(pname, nullptr);
if (Q_UNLIKELY(!parameter)) {
qCWarning(GLTFImporterLog, "uniform %ls defined in instanceProgram but not as parameter",
qUtf16PrintableImpl(pname));
continue;
}
//Check if the parameter has a standard uniform semantic
const auto paramDataIt = m_parameterDataDict.find(parameter);
if (hasStandardUniformNameFromSemantic(paramDataIt->semantic)) {
t->removeParameter(parameter);
m_parameterDataDict.erase(paramDataIt);
paramDict.remove(pname);
delete parameter;
}
} // of program-instance uniforms
m_techniqueParameters.insert(t, paramDict.values());
populateRenderStates(pass, jsonObject.value(KEY_STATES).toObject());
t->addRenderPass(pass);
} else {
// Qt3D exported custom technique
// Graphics API filter
t->graphicsApiFilter()->setApi(QGraphicsApiFilter::Api(gabifilter.value(KEY_API).toInt()));
t->graphicsApiFilter()->setMajorVersion(gabifilter.value(KEY_MAJORVERSION).toInt());
t->graphicsApiFilter()->setMinorVersion(gabifilter.value(KEY_MINORVERSION).toInt());
t->graphicsApiFilter()->setProfile(QGraphicsApiFilter::OpenGLProfile(
gabifilter.value(KEY_PROFILE).toInt()));
t->graphicsApiFilter()->setVendor(gabifilter.value(KEY_VENDOR).toString());
QStringList extensionList;
QJsonArray extArray = gabifilter.value(KEY_EXTENSIONS).toArray();
for (const QJsonValue &extValue : extArray)
extensionList << extValue.toString();
t->graphicsApiFilter()->setExtensions(extensionList);
// Filter keys (we will assume filter keys are always strings or integers)
const QJsonObject fkObj = jsonObject.value(KEY_FILTERKEYS).toObject();
for (auto it = fkObj.begin(), end = fkObj.end(); it != end; ++it)
t->addFilterKey(buildFilterKey(it.key(), it.value()));
t->setObjectName(jsonObject.value(KEY_NAME).toString());
// Parameters
const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject();
for (auto it = params.begin(), end = params.end(); it != end; ++it)
t->addParameter(buildParameter(it.key(), it.value().toObject()));
// Render passes
const QJsonArray passArray = jsonObject.value(KEY_RENDERPASSES).toArray();
for (const QJsonValue &passValue : passArray) {
const QString passName = passValue.toString();
QRenderPass *pass = m_renderPasses.value(passName);
if (pass) {
t->addRenderPass(pass);
} else {
qCWarning(GLTFImporterLog, "Render pass %ls missing for technique %ls",
qUtf16PrintableImpl(passName), qUtf16PrintableImpl(id));
}
}
}
m_techniques[id] = t;
}
void GLTFImporter::processJSONAccessor(const QString &id, const QJsonObject& json)
{
m_accessorDict[id] = AccessorData(json, m_majorVersion, m_minorVersion);
}
void GLTFImporter::processJSONMesh(const QString &id, const QJsonObject &json)
{
const QString meshName = json.value(KEY_NAME).toString();
const QString meshType = json.value(KEY_TYPE).toString();
if (meshType.isEmpty()) {
// Custom mesh
const QJsonArray primitivesArray = json.value(KEY_PRIMITIVES).toArray();
for (const QJsonValue &primitiveValue : primitivesArray) {
const QJsonObject primitiveObject = primitiveValue.toObject();
const QJsonValue type = primitiveObject.value(KEY_MODE);
const QJsonValue matValue = primitiveObject.value(KEY_MATERIAL);
const QString material = (m_majorVersion > 1) ? QString::number(matValue.toInt()) : matValue.toString();
QGeometryRenderer *geometryRenderer = new QGeometryRenderer;
QGeometry *meshGeometry = new QGeometry(geometryRenderer);
//Set Primitive Type
if (type.isUndefined()) {
geometryRenderer->setPrimitiveType(QGeometryRenderer::Triangles);
} else {
geometryRenderer->setPrimitiveType(static_cast<QGeometryRenderer::PrimitiveType>(type.toInt()));
}
//Save Material for mesh
m_meshMaterialDict[geometryRenderer] = material;
const QJsonObject attrs = primitiveObject.value(KEY_ATTRIBUTES).toObject();
for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) {
const QString k = (m_majorVersion > 1) ? QString::number(it.value().toInt()) : it.value().toString();
const auto accessorIt = qAsConst(m_accessorDict).find(k);
if (Q_UNLIKELY(accessorIt == m_accessorDict.cend())) {
qCWarning(GLTFImporterLog, "unknown attribute accessor: %ls on mesh %ls",
qUtf16PrintableImpl(k), qUtf16PrintableImpl(id));
continue;
}
const QString attrName = it.key();
QString attributeName = standardAttributeNameFromSemantic(attrName);
if (attributeName.isEmpty())
attributeName = attrName;
//Get buffer handle for accessor
Qt3DRender::QBuffer *buffer = m_buffers.value(accessorIt->bufferViewName, nullptr);
if (Q_UNLIKELY(!buffer)) {
qCWarning(GLTFImporterLog, "unknown buffer-view: %ls processing accessor: %ls",
qUtf16PrintableImpl(accessorIt->bufferViewName),
qUtf16PrintableImpl(id));
continue;
}
QAttribute *attribute = new QAttribute(buffer,
attributeName,
accessorIt->type,
accessorIt->dataSize,
accessorIt->count,
accessorIt->offset,
accessorIt->stride);
attribute->setAttributeType(QAttribute::VertexAttribute);
meshGeometry->addAttribute(attribute);
}
const auto indices = primitiveObject.value(KEY_INDICES);
if (!indices.isUndefined()) {
const QString accIndex = (m_majorVersion > 1) ? QString::number(indices.toInt()) : indices.toString();
const auto accessorIt = qAsConst(m_accessorDict).find(accIndex);
if (Q_UNLIKELY(accessorIt == m_accessorDict.cend())) {
qCWarning(GLTFImporterLog, "unknown index accessor: %ls on mesh %ls",
qUtf16PrintableImpl(accIndex), qUtf16PrintableImpl(id));
} else {
//Get buffer handle for accessor
Qt3DRender::QBuffer *buffer = m_buffers.value(accessorIt->bufferViewName, nullptr);
if (Q_UNLIKELY(!buffer)) {
qCWarning(GLTFImporterLog, "unknown buffer-view: %ls processing accessor: %ls",
qUtf16PrintableImpl(accessorIt->bufferViewName),
qUtf16PrintableImpl(id));
continue;
}
QAttribute *attribute = new QAttribute(buffer,
accessorIt->type,
accessorIt->dataSize,
accessorIt->count,
accessorIt->offset,
accessorIt->stride);
attribute->setAttributeType(QAttribute::IndexAttribute);
meshGeometry->addAttribute(attribute);
}
} // of has indices
geometryRenderer->setGeometry(meshGeometry);
geometryRenderer->setObjectName(meshName);
m_meshDict.insert(id, geometryRenderer);
} // of primitives iteration
} else {
QGeometryRenderer *mesh = nullptr;
if (meshType == QStringLiteral("cone")) {
mesh = new QConeMesh;
} else if (meshType == QStringLiteral("cuboid")) {
mesh = new QCuboidMesh;
} else if (meshType == QStringLiteral("cylinder")) {
mesh = new QCylinderMesh;
} else if (meshType == QStringLiteral("plane")) {
mesh = new QPlaneMesh;
} else if (meshType == QStringLiteral("sphere")) {
mesh = new QSphereMesh;
} else if (meshType == QStringLiteral("torus")) {
mesh = new QTorusMesh;
} else {
qCWarning(GLTFImporterLog,
"Invalid mesh type: %ls for mesh: %ls",
qUtf16PrintableImpl(meshType),
qUtf16PrintableImpl(id));
}
if (mesh) {
// Read and set properties
const QJsonObject propObj = json.value(KEY_PROPERTIES).toObject();
for (auto it = propObj.begin(), end = propObj.end(); it != end; ++it) {
const QByteArray propName = it.key().toLatin1();
// Basic mesh types only have bool, int, float, and QSize type properties
if (it.value().isBool()) {
mesh->setProperty(propName.constData(), QVariant(it.value().toBool()));
} else if (it.value().isArray()) {
const QJsonArray valueArray = it.value().toArray();
if (valueArray.size() == 2) {
QSize size;
size.setWidth(valueArray.at(0).toInt());
size.setHeight(valueArray.at(1).toInt());
mesh->setProperty(propName.constData(), QVariant(size));
}
} else {
const QVariant::Type propType = mesh->property(propName.constData()).type();
if (propType == QVariant::Int) {
mesh->setProperty(propName.constData(), QVariant(it.value().toInt()));
} else {
mesh->setProperty(propName.constData(),
QVariant(float(it.value().toDouble())));
}
}
}
mesh->setObjectName(meshName);
m_meshMaterialDict[mesh] = (m_majorVersion > 1) ?
QString::number(json.value(KEY_MATERIAL).toInt()) :
json.value(KEY_MATERIAL).toString();
m_meshDict.insert(id, mesh);
}
}
}
void GLTFImporter::processJSONImage(const QString &id, const QJsonObject &jsonObject)
{
QString path = jsonObject.value(KEY_URI).toString();
if (!isEmbeddedResource(path)) {
QFileInfo info(m_basePath, path);
if (Q_UNLIKELY(!info.exists())) {
qCWarning(GLTFImporterLog, "can't find image %ls from path %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(path));
return;
}
m_imagePaths[id] = info.absoluteFilePath();
} else {
const QByteArray base64Data = path.toLatin1().remove(0, path.indexOf(",") + 1);
QImage image;
image.loadFromData(QByteArray::fromBase64(base64Data));
m_imageData[id] = image;
}
}
void GLTFImporter::processJSONTexture(const QString &id, const QJsonObject &jsonObject)
{
QJsonValue jsonVal = jsonObject.value(KEY_TARGET);
if (!jsonVal.isUndefined()) {
int target = jsonVal.toInt(GL_TEXTURE_2D);
//TODO: support other targets that GL_TEXTURE_2D (though the spec doesn't support anything else)
if (Q_UNLIKELY(target != GL_TEXTURE_2D)) {
qCWarning(GLTFImporterLog, "unsupported texture target: %d", target);
return;
}
}
QTexture2D* tex = new QTexture2D;
// TODO: Choose suitable internal format - may vary on OpenGL context type
//int pixelFormat = jsonObj.value(KEY_FORMAT).toInt(GL_RGBA);
int internalFormat = GL_RGBA;
jsonVal = jsonObject.value(KEY_INTERNAL_FORMAT);
if (!jsonVal.isUndefined())
internalFormat = jsonObject.value(KEY_INTERNAL_FORMAT).toInt(GL_RGBA);
tex->setFormat(static_cast<QAbstractTexture::TextureFormat>(internalFormat));
QJsonValue srcValue = jsonObject.value(KEY_SOURCE);
QString source = (m_majorVersion > 1) ? QString::number(srcValue.toInt()) : srcValue.toString();
const auto imagIt = qAsConst(m_imagePaths).find(source);
if (Q_UNLIKELY(imagIt == m_imagePaths.cend())) {
// if an image is not found in paths, it probably means
// it was an embedded resource, referenced in m_imageData
const auto embImgIt = qAsConst(m_imageData).find(source);
if (Q_UNLIKELY(embImgIt == m_imageData.cend())) {
qCWarning(GLTFImporterLog, "texture %ls references missing image %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(source));
return;
}
QImage img = embImgIt.value();
GLTFRawTextureImage *imageData = new GLTFRawTextureImage();
imageData->setImage(img);
tex->addTextureImage(imageData);
} else {
QTextureImage *texImage = new QTextureImage(tex);
texImage->setMirrored(false);
texImage->setSource(QUrl::fromLocalFile(imagIt.value()));
tex->addTextureImage(texImage);
}
setTextureSamplerInfo(id, jsonObject, tex);
m_textures[id] = tex;
}
void GLTFImporter::processJSONExtensions(const QString &id, const QJsonObject &jsonObject)
{
// Lights are defined in "KHR_materials_common" property of "extensions" property of the top
// level GLTF item.
if (id == KEY_COMMON_MAT) {
const auto lights = jsonObject.value(KEY_LIGHTS).toObject();
const auto keys = lights.keys();
for (const auto &lightKey : keys) {
const auto light = lights.value(lightKey).toObject();
auto lightType = light.value(KEY_TYPE).toString();
const auto lightValues = light.value(lightType).toObject();
QAbstractLight *lightComp = nullptr;
if (lightType == KEY_DIRECTIONAL_LIGHT) {
auto dirLight = new QDirectionalLight;
dirLight->setWorldDirection(
jsonArrToVec3(lightValues.value(KEY_DIRECTION).toArray()));
lightComp = dirLight;
} else if (lightType == KEY_SPOT_LIGHT) {
auto spotLight = new QSpotLight;
spotLight->setLocalDirection(
jsonArrToVec3(lightValues.value(KEY_DIRECTION).toArray()));
spotLight->setConstantAttenuation(
lightValues.value(KEY_CONST_ATTENUATION).toDouble());
spotLight->setLinearAttenuation(
lightValues.value(KEY_LINEAR_ATTENUATION).toDouble());
spotLight->setQuadraticAttenuation(
lightValues.value(KEY_QUAD_ATTENUATION).toDouble());
spotLight->setCutOffAngle(
lightValues.value(KEY_FALLOFF_ANGLE).toDouble());
lightComp = spotLight;
} else if (lightType == KEY_POINT_LIGHT) {
auto pointLight = new QPointLight;
pointLight->setConstantAttenuation(
lightValues.value(KEY_CONST_ATTENUATION).toDouble());
pointLight->setLinearAttenuation(
lightValues.value(KEY_LINEAR_ATTENUATION).toDouble());
pointLight->setQuadraticAttenuation(
lightValues.value(KEY_QUAD_ATTENUATION).toDouble());
lightComp = pointLight;
} else if (lightType == KEY_AMBIENT_LIGHT) {
qCWarning(GLTFImporterLog, "Ambient lights are not supported.");
} else {
qCWarning(GLTFImporterLog, "Unknown light type: %ls",
qUtf16PrintableImpl(lightType));
}
if (lightComp) {
auto colorVal = lightValues.value(KEY_COLOR);
lightComp->setColor(vec4ToQColor(parameterValueFromJSON(GL_FLOAT_VEC4, colorVal)));
lightComp->setIntensity(lightValues.value(KEY_INTENSITY).toDouble());
lightComp->setObjectName(light.value(KEY_NAME).toString());
m_lights.insert(lightKey, lightComp);
}
}
}
}
void GLTFImporter::processJSONEffect(const QString &id, const QJsonObject &jsonObject)
{
QEffect *effect = new QEffect;
renameFromJson(jsonObject, effect);
const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject();
for (auto it = params.begin(), end = params.end(); it != end; ++it)
effect->addParameter(buildParameter(it.key(), it.value().toObject()));
const QJsonArray techArray = jsonObject.value(KEY_TECHNIQUES).toArray();
for (const QJsonValue &techValue : techArray) {
const QString techName = techValue.toString();
QTechnique *tech = m_techniques.value(techName);
if (tech) {
effect->addTechnique(tech);
} else {
qCWarning(GLTFImporterLog, "Technique pass %ls missing for effect %ls",
qUtf16PrintableImpl(techName), qUtf16PrintableImpl(id));
}
}
m_effects[id] = effect;
}
void GLTFImporter::processJSONRenderPass(const QString &id, const QJsonObject &jsonObject)
{
QRenderPass *pass = new QRenderPass;
const QJsonObject passFkObj = jsonObject.value(KEY_FILTERKEYS).toObject();
for (auto it = passFkObj.begin(), end = passFkObj.end(); it != end; ++it)
pass->addFilterKey(buildFilterKey(it.key(), it.value()));
const QJsonObject params = jsonObject.value(KEY_PARAMETERS).toObject();
for (auto it = params.begin(), end = params.end(); it != end; ++it)
pass->addParameter(buildParameter(it.key(), it.value().toObject()));
populateRenderStates(pass, jsonObject.value(KEY_STATES).toObject());
addProgramToPass(pass, jsonObject.value(KEY_PROGRAM).toString());
renameFromJson(jsonObject, pass);
m_renderPasses[id] = pass;
}
/*!
Loads raw data from the GLTF file into the buffer.
*/
void GLTFImporter::loadBufferData()
{
for (auto &bufferData : m_bufferDatas) {
if (!bufferData.data) {
bufferData.data = new QByteArray(resolveLocalData(bufferData.path));
}
}
}
/*!
Removes all data from the buffer.
*/
void GLTFImporter::unloadBufferData()
{
for (const auto &bufferData : qAsConst(m_bufferDatas)) {
QByteArray *data = bufferData.data;
delete data;
}
}
QByteArray GLTFImporter::resolveLocalData(const QString &path) const
{
QDir d(m_basePath);
Q_ASSERT(d.exists());
// check for embedded data
if (isEmbeddedResource(path)) {
const QByteArray base64Data = path.toLatin1().remove(0, path.indexOf(",") + 1);
return QByteArray::fromBase64(base64Data);
} else {
const QString absPath = d.absoluteFilePath(path);
QFile f(absPath);
f.open(QIODevice::ReadOnly);
return f.readAll();
}
}
QVariant GLTFImporter::parameterValueFromJSON(int type, const QJsonValue &value) const
{
if (value.isBool()) {
if (type == GL_BOOL)
return QVariant(static_cast<GLboolean>(value.toBool()));
} else if (value.isString()) {
if (type == GL_SAMPLER_2D) {
//Textures are special because we need to do a lookup to return the
//QAbstractTexture
QString textureId = value.toString();
const auto it = m_textures.find(textureId);
if (Q_UNLIKELY(it == m_textures.end())) {
qCWarning(GLTFImporterLog, "unknown texture %ls", qUtf16PrintableImpl(textureId));
return QVariant();
} else {
return QVariant::fromValue(it.value());
}
}
} else if (value.isDouble()) {
switch (type) {
case GL_BYTE:
return QVariant(static_cast<GLbyte>(value.toInt()));
case GL_UNSIGNED_BYTE:
return QVariant(static_cast<GLubyte>(value.toInt()));
case GL_SHORT:
return QVariant(static_cast<GLshort>(value.toInt()));
case GL_UNSIGNED_SHORT:
return QVariant(static_cast<GLushort>(value.toInt()));
case GL_INT:
return QVariant(static_cast<GLint>(value.toInt()));
case GL_UNSIGNED_INT:
return QVariant(static_cast<GLuint>(value.toInt()));
case GL_FLOAT:
return QVariant(static_cast<GLfloat>(value.toDouble()));
default:
break;
}
} else if (value.isArray()) {
const QJsonArray valueArray = value.toArray();
QVector2D vector2D;
QVector3D vector3D;
QVector4D vector4D;
QVector<float> dataMat2(4, 0.0f);
QVector<float> dataMat3(9, 0.0f);
switch (type) {
case GL_BYTE:
return QVariant(static_cast<GLbyte>(valueArray.first().toInt()));
case GL_UNSIGNED_BYTE:
return QVariant(static_cast<GLubyte>(valueArray.first().toInt()));
case GL_SHORT:
return QVariant(static_cast<GLshort>(valueArray.first().toInt()));
case GL_UNSIGNED_SHORT:
return QVariant(static_cast<GLushort>(valueArray.first().toInt()));
case GL_INT:
return QVariant(static_cast<GLint>(valueArray.first().toInt()));
case GL_UNSIGNED_INT:
return QVariant(static_cast<GLuint>(valueArray.first().toInt()));
case GL_FLOAT:
return QVariant(static_cast<GLfloat>(valueArray.first().toDouble()));
case GL_FLOAT_VEC2:
vector2D.setX(static_cast<GLfloat>(valueArray.at(0).toDouble()));
vector2D.setY(static_cast<GLfloat>(valueArray.at(1).toDouble()));
return QVariant(vector2D);
case GL_FLOAT_VEC3:
vector3D.setX(static_cast<GLfloat>(valueArray.at(0).toDouble()));
vector3D.setY(static_cast<GLfloat>(valueArray.at(1).toDouble()));
vector3D.setZ(static_cast<GLfloat>(valueArray.at(2).toDouble()));
return QVariant(vector3D);
case GL_FLOAT_VEC4:
vector4D.setX(static_cast<GLfloat>(valueArray.at(0).toDouble()));
vector4D.setY(static_cast<GLfloat>(valueArray.at(1).toDouble()));
vector4D.setZ(static_cast<GLfloat>(valueArray.at(2).toDouble()));
vector4D.setW(static_cast<GLfloat>(valueArray.at(3).toDouble()));
return QVariant(vector4D);
case GL_INT_VEC2:
vector2D.setX(static_cast<GLint>(valueArray.at(0).toInt()));
vector2D.setY(static_cast<GLint>(valueArray.at(1).toInt()));
return QVariant(vector2D);
case GL_INT_VEC3:
vector3D.setX(static_cast<GLint>(valueArray.at(0).toInt()));
vector3D.setY(static_cast<GLint>(valueArray.at(1).toInt()));
vector3D.setZ(static_cast<GLint>(valueArray.at(2).toInt()));
return QVariant(vector3D);
case GL_INT_VEC4:
vector4D.setX(static_cast<GLint>(valueArray.at(0).toInt()));
vector4D.setY(static_cast<GLint>(valueArray.at(1).toInt()));
vector4D.setZ(static_cast<GLint>(valueArray.at(2).toInt()));
vector4D.setW(static_cast<GLint>(valueArray.at(3).toInt()));
return QVariant(vector4D);
case GL_BOOL:
return QVariant(static_cast<GLboolean>(valueArray.first().toBool()));
case GL_BOOL_VEC2:
vector2D.setX(static_cast<GLboolean>(valueArray.at(0).toBool()));
vector2D.setY(static_cast<GLboolean>(valueArray.at(1).toBool()));
return QVariant(vector2D);
case GL_BOOL_VEC3:
vector3D.setX(static_cast<GLboolean>(valueArray.at(0).toBool()));
vector3D.setY(static_cast<GLboolean>(valueArray.at(1).toBool()));
vector3D.setZ(static_cast<GLboolean>(valueArray.at(2).toBool()));
return QVariant(vector3D);
case GL_BOOL_VEC4:
vector4D.setX(static_cast<GLboolean>(valueArray.at(0).toBool()));
vector4D.setY(static_cast<GLboolean>(valueArray.at(1).toBool()));
vector4D.setZ(static_cast<GLboolean>(valueArray.at(2).toBool()));
vector4D.setW(static_cast<GLboolean>(valueArray.at(3).toBool()));
return QVariant(vector4D);
case GL_FLOAT_MAT2:
//Matrix2x2 is in Row Major ordering (so we need to convert)
dataMat2[0] = static_cast<GLfloat>(valueArray.at(0).toDouble());
dataMat2[1] = static_cast<GLfloat>(valueArray.at(2).toDouble());
dataMat2[2] = static_cast<GLfloat>(valueArray.at(1).toDouble());
dataMat2[3] = static_cast<GLfloat>(valueArray.at(3).toDouble());
return QVariant::fromValue(QMatrix2x2(dataMat2.constData()));
case GL_FLOAT_MAT3:
//Matrix3x3 is in Row Major ordering (so we need to convert)
dataMat3[0] = static_cast<GLfloat>(valueArray.at(0).toDouble());
dataMat3[1] = static_cast<GLfloat>(valueArray.at(3).toDouble());
dataMat3[2] = static_cast<GLfloat>(valueArray.at(6).toDouble());
dataMat3[3] = static_cast<GLfloat>(valueArray.at(1).toDouble());
dataMat3[4] = static_cast<GLfloat>(valueArray.at(4).toDouble());
dataMat3[5] = static_cast<GLfloat>(valueArray.at(7).toDouble());
dataMat3[6] = static_cast<GLfloat>(valueArray.at(2).toDouble());
dataMat3[7] = static_cast<GLfloat>(valueArray.at(5).toDouble());
dataMat3[8] = static_cast<GLfloat>(valueArray.at(8).toDouble());
return QVariant::fromValue(QMatrix3x3(dataMat3.constData()));
case GL_FLOAT_MAT4:
//Matrix4x4 is Column Major ordering
return QVariant(QMatrix4x4(static_cast<GLfloat>(valueArray.at(0).toDouble()),
static_cast<GLfloat>(valueArray.at(1).toDouble()),
static_cast<GLfloat>(valueArray.at(2).toDouble()),
static_cast<GLfloat>(valueArray.at(3).toDouble()),
static_cast<GLfloat>(valueArray.at(4).toDouble()),
static_cast<GLfloat>(valueArray.at(5).toDouble()),
static_cast<GLfloat>(valueArray.at(6).toDouble()),
static_cast<GLfloat>(valueArray.at(7).toDouble()),
static_cast<GLfloat>(valueArray.at(8).toDouble()),
static_cast<GLfloat>(valueArray.at(9).toDouble()),
static_cast<GLfloat>(valueArray.at(10).toDouble()),
static_cast<GLfloat>(valueArray.at(11).toDouble()),
static_cast<GLfloat>(valueArray.at(12).toDouble()),
static_cast<GLfloat>(valueArray.at(13).toDouble()),
static_cast<GLfloat>(valueArray.at(14).toDouble()),
static_cast<GLfloat>(valueArray.at(15).toDouble())));
case GL_SAMPLER_2D:
return QVariant(valueArray.at(0).toString());
}
}
return QVariant();
}
QAttribute::VertexBaseType GLTFImporter::accessorTypeFromJSON(int componentType)
{
if (componentType == GL_BYTE) {
return QAttribute::Byte;
} else if (componentType == GL_UNSIGNED_BYTE) {
return QAttribute::UnsignedByte;
} else if (componentType == GL_SHORT) {
return QAttribute::Short;
} else if (componentType == GL_UNSIGNED_SHORT) {
return QAttribute::UnsignedShort;
} else if (componentType == GL_UNSIGNED_INT) {
return QAttribute::UnsignedInt;
} else if (componentType == GL_FLOAT) {
return QAttribute::Float;
}
//There shouldn't be an invalid case here
qCWarning(GLTFImporterLog, "unsupported accessor type %d", componentType);
return QAttribute::Float;
}
uint GLTFImporter::accessorDataSizeFromJson(const QString &type)
{
QString typeName = type.toUpper();
if (typeName == QLatin1String("SCALAR"))
return 1;
if (typeName == QLatin1String("VEC2"))
return 2;
if (typeName == QLatin1String("VEC3"))
return 3;
if (typeName == QLatin1String("VEC4"))
return 4;
if (typeName == QLatin1String("MAT2"))
return 4;
if (typeName == QLatin1String("MAT3"))
return 9;
if (typeName == QLatin1String("MAT4"))
return 16;
return 0;
}
QRenderState *GLTFImporter::buildStateEnable(int state)
{
int type = 0;
//By calling buildState with QJsonValue(), a Render State with
//default values is created.
switch (state) {
case GL_BLEND:
//It doesn't make sense to handle this state alone
return nullptr;
case GL_CULL_FACE:
return buildState(QStringLiteral("cullFace"), QJsonValue(), type);
case GL_DEPTH_TEST:
return buildState(QStringLiteral("depthFunc"), QJsonValue(), type);
case GL_POLYGON_OFFSET_FILL:
return buildState(QStringLiteral("polygonOffset"), QJsonValue(), type);
case GL_SAMPLE_ALPHA_TO_COVERAGE:
return new QAlphaCoverage();
case GL_SCISSOR_TEST:
return buildState(QStringLiteral("scissor"), QJsonValue(), type);
case GL_DITHER: // Qt3D Custom
return new QDithering();
case 0x809D: // GL_MULTISAMPLE - Qt3D Custom
return new QMultiSampleAntiAliasing();
case 0x884F: // GL_TEXTURE_CUBE_MAP_SEAMLESS - Qt3D Custom
return new QSeamlessCubemap();
default:
break;
}
qCWarning(GLTFImporterLog, "unsupported render state: %d", state);
return nullptr;
}
QRenderState* GLTFImporter::buildState(const QString& functionName, const QJsonValue &value, int &type)
{
type = -1;
QJsonArray values = value.toArray();
if (functionName == QLatin1String("blendColor")) {
type = GL_BLEND;
//TODO: support render state blendColor
qCWarning(GLTFImporterLog, "unsupported render state: %ls", qUtf16PrintableImpl(functionName));
return nullptr;
}
if (functionName == QLatin1String("blendEquationSeparate")) {
type = GL_BLEND;
//TODO: support settings blendEquation alpha
QBlendEquation *blendEquation = new QBlendEquation;
blendEquation->setBlendFunction((QBlendEquation::BlendFunction)values.at(0).toInt(GL_FUNC_ADD));
return blendEquation;
}
if (functionName == QLatin1String("blendFuncSeparate")) {
type = GL_BLEND;
QBlendEquationArguments *blendArgs = new QBlendEquationArguments;
blendArgs->setSourceRgb((QBlendEquationArguments::Blending)values.at(0).toInt(GL_ONE));
blendArgs->setSourceAlpha((QBlendEquationArguments::Blending)values.at(1).toInt(GL_ONE));
blendArgs->setDestinationRgb((QBlendEquationArguments::Blending)values.at(2).toInt(GL_ZERO));
blendArgs->setDestinationAlpha((QBlendEquationArguments::Blending)values.at(3).toInt(GL_ZERO));
blendArgs->setBufferIndex(values.at(4).toInt(-1));
return blendArgs;
}
if (functionName == QLatin1String("colorMask")) {
QColorMask *colorMask = new QColorMask;
colorMask->setRedMasked(values.at(0).toBool(true));
colorMask->setGreenMasked(values.at(1).toBool(true));
colorMask->setBlueMasked(values.at(2).toBool(true));
colorMask->setAlphaMasked(values.at(3).toBool(true));
return colorMask;
}
if (functionName == QLatin1String("cullFace")) {
type = GL_CULL_FACE;
QCullFace *cullFace = new QCullFace;
cullFace->setMode((QCullFace::CullingMode)values.at(0).toInt(GL_BACK));
return cullFace;
}
if (functionName == QLatin1String("depthFunc")) {
type = GL_DEPTH_TEST;
QDepthTest *depthTest = new QDepthTest;
depthTest->setDepthFunction((QDepthTest::DepthFunction)values.at(0).toInt(GL_LESS));
return depthTest;
}
if (functionName == QLatin1String("depthMask")) {
if (!values.at(0).toBool(true)) {
QNoDepthMask *depthMask = new QNoDepthMask;
return depthMask;
}
return nullptr;
}
if (functionName == QLatin1String("depthRange")) {
type = GL_DEPTH_RANGE;
QDepthRange *depthRange = new QDepthRange;
depthRange->setNearValue(values.at(0).toDouble(0.0));
depthRange->setFarValue(values.at(1).toDouble(1.0));
return depthRange;
}
if (functionName == QLatin1String("frontFace")) {
QFrontFace *frontFace = new QFrontFace;
frontFace->setDirection((QFrontFace::WindingDirection)values.at(0).toInt(GL_CCW));
return frontFace;
}
if (functionName == QLatin1String("lineWidth")) {
//TODO: support render state lineWidth
qCWarning(GLTFImporterLog, "unsupported render state: %ls", qUtf16PrintableImpl(functionName));
return nullptr;
}
if (functionName == QLatin1String("polygonOffset")) {
type = GL_POLYGON_OFFSET_FILL;
QPolygonOffset *polygonOffset = new QPolygonOffset;
polygonOffset->setScaleFactor((float)values.at(0).toDouble(0.0f));
polygonOffset->setDepthSteps((float)values.at(1).toDouble(0.0f));
return polygonOffset;
}
if (functionName == QLatin1String("scissor")) {
type = GL_SCISSOR_TEST;
QScissorTest *scissorTest = new QScissorTest;
scissorTest->setLeft(values.at(0).toDouble(0.0f));
scissorTest->setBottom(values.at(1).toDouble(0.0f));
scissorTest->setWidth(values.at(2).toDouble(0.0f));
scissorTest->setHeight(values.at(3).toDouble(0.0f));
return scissorTest;
}
// Qt3D custom functions
if (functionName == QLatin1String("alphaTest")) {
QAlphaTest *args = new QAlphaTest;
args->setAlphaFunction(QAlphaTest::AlphaFunction(values.at(0).toInt()));
args->setReferenceValue(float(values.at(1).toDouble()));
return args;
}
if (functionName == QLatin1String("clipPlane")) {
QClipPlane *args = new QClipPlane;
args->setPlaneIndex(values.at(0).toInt());
args->setNormal(QVector3D(float(values.at(1).toDouble()),
float(values.at(2).toDouble()),
float(values.at(3).toDouble())));
args->setDistance(float(values.at(4).toDouble()));
return args;
}
if (functionName == QLatin1String("pointSize")) {
QPointSize *pointSize = new QPointSize;
pointSize->setSizeMode(QPointSize::SizeMode(values.at(0).toInt(QPointSize::Programmable)));
pointSize->setValue(float(values.at(1).toDouble()));
return pointSize;
}
if (functionName == QLatin1String("stencilMask")) {
QStencilMask *stencilMask = new QStencilMask;
stencilMask->setFrontOutputMask(uint(values.at(0).toInt()));
stencilMask->setBackOutputMask(uint(values.at(1).toInt()));
return stencilMask;
}
if (functionName == QLatin1String("stencilOperation")) {
QStencilOperation *stencilOperation = new QStencilOperation;
stencilOperation->front()->setStencilTestFailureOperation(
QStencilOperationArguments::Operation(values.at(0).toInt(
QStencilOperationArguments::Keep)));
stencilOperation->front()->setDepthTestFailureOperation(
QStencilOperationArguments::Operation(values.at(1).toInt(
QStencilOperationArguments::Keep)));
stencilOperation->front()->setAllTestsPassOperation(
QStencilOperationArguments::Operation(values.at(2).toInt(
QStencilOperationArguments::Keep)));
stencilOperation->back()->setStencilTestFailureOperation(
QStencilOperationArguments::Operation(values.at(3).toInt(
QStencilOperationArguments::Keep)));
stencilOperation->back()->setDepthTestFailureOperation(
QStencilOperationArguments::Operation(values.at(4).toInt(
QStencilOperationArguments::Keep)));
stencilOperation->back()->setAllTestsPassOperation(
QStencilOperationArguments::Operation(values.at(5).toInt(
QStencilOperationArguments::Keep)));
return stencilOperation;
}
if (functionName == QLatin1String("stencilTest")) {
QStencilTest *stencilTest = new QStencilTest;
stencilTest->front()->setComparisonMask(uint(values.at(0).toInt()));
stencilTest->front()->setReferenceValue(values.at(1).toInt());
stencilTest->front()->setStencilFunction(
QStencilTestArguments::StencilFunction(values.at(2).toInt(
QStencilTestArguments::Never)));
stencilTest->back()->setComparisonMask(uint(values.at(3).toInt()));
stencilTest->back()->setReferenceValue(values.at(4).toInt());
stencilTest->back()->setStencilFunction(
QStencilTestArguments::StencilFunction(values.at(5).toInt(
QStencilTestArguments::Never)));
return stencilTest;
}
qCWarning(GLTFImporterLog, "unsupported render state: %ls", qUtf16PrintableImpl(functionName));
return nullptr;
}
QParameter *GLTFImporter::buildParameter(const QString &key, const QJsonObject &paramObj)
{
QParameter *p = new QParameter;
p->setName(key);
QJsonValue value = paramObj.value(KEY_VALUE);
if (!value.isUndefined()) {
int dataType = paramObj.value(KEY_TYPE).toInt();
p->setValue(parameterValueFromJSON(dataType, value));
}
return p;
}
void GLTFImporter::populateRenderStates(QRenderPass *pass, const QJsonObject &states)
{
// Process states to enable
const QJsonArray enableStatesArray = states.value(KEY_ENABLE).toArray();
QVector<int> enableStates;
for (const QJsonValue &enableValue : enableStatesArray)
enableStates.append(enableValue.toInt());
// Process the list of state functions
const QJsonObject functions = states.value(KEY_FUNCTIONS).toObject();
for (auto it = functions.begin(), end = functions.end(); it != end; ++it) {
int enableStateType = 0;
QRenderState *renderState = buildState(it.key(), it.value(), enableStateType);
if (renderState != nullptr) {
//Remove the need to set a default state values for enableStateType
enableStates.removeOne(enableStateType);
pass->addRenderState(renderState);
}
}
// Create render states with default values for any remaining enable states
for (int enableState : qAsConst(enableStates)) {
QRenderState *renderState = buildStateEnable(enableState);
if (renderState != nullptr)
pass->addRenderState(renderState);
}
}
void GLTFImporter::addProgramToPass(QRenderPass *pass, const QString &progName)
{
const auto progIt = qAsConst(m_programs).find(progName);
if (Q_UNLIKELY(progIt == m_programs.cend()))
qCWarning(GLTFImporterLog, "missing program %ls", qUtf16PrintableImpl(progName));
else
pass->setShaderProgram(progIt.value());
}
void GLTFImporter::setTextureSamplerInfo(const QString &id, const QJsonObject &jsonObj, QTexture2D *tex)
{
QJsonObject sampler;
const QJsonValue jsonValue = jsonObj.value(KEY_SAMPLER);
if (jsonValue.isUndefined())
return;
if (m_majorVersion > 1) {
const int samplerId = jsonValue.toInt();
const QJsonArray sArray = m_json.object().value(KEY_SAMPLERS).toArray();
if (Q_UNLIKELY(samplerId >= sArray.count())) {
qCWarning(GLTFImporterLog, "texture %ls references unknown sampler %d",
qUtf16PrintableImpl(id), samplerId);
return;
}
sampler = sArray[samplerId].toObject();
} else {
const QString samplerId = jsonValue.toString();
const QJsonValue samplersDictValue = m_json.object().value(KEY_SAMPLERS).toObject().value(samplerId);
if (Q_UNLIKELY(samplersDictValue.isUndefined())) {
qCWarning(GLTFImporterLog, "texture %ls references unknown sampler %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(samplerId));
return;
}
sampler = samplersDictValue.toObject();
}
tex->setWrapMode(QTextureWrapMode(static_cast<QTextureWrapMode::WrapMode>(sampler.value(KEY_WRAP_S).toInt())));
tex->setMinificationFilter(static_cast<QAbstractTexture::Filter>(sampler.value(KEY_MIN_FILTER).toInt()));
if (tex->minificationFilter() == QAbstractTexture::NearestMipMapLinear ||
tex->minificationFilter() == QAbstractTexture::LinearMipMapNearest ||
tex->minificationFilter() == QAbstractTexture::NearestMipMapNearest ||
tex->minificationFilter() == QAbstractTexture::LinearMipMapLinear) {
tex->setGenerateMipMaps(true);
}
tex->setMagnificationFilter(static_cast<QAbstractTexture::Filter>(sampler.value(KEY_MAG_FILTER).toInt()));
}
GLTFRawTextureImage::GLTFRawTextureImage(QNode *parent)
: QAbstractTextureImage(parent)
{
}
QTextureImageDataGeneratorPtr GLTFRawTextureImage::dataGenerator() const
{
return QTextureImageDataGeneratorPtr(new GLTFRawTextureImageFunctor(m_image));
}
void GLTFRawTextureImage::setImage(const QImage &image)
{
if (image != m_image) {
m_image = image;
notifyDataGeneratorChanged();
}
}
GLTFRawTextureImage::GLTFRawTextureImageFunctor::GLTFRawTextureImageFunctor(const QImage &image)
: QTextureImageDataGenerator()
, m_image(image)
{
}
QTextureImageDataPtr GLTFRawTextureImage::GLTFRawTextureImageFunctor::operator()()
{
QTextureImageDataPtr dataPtr = QTextureImageDataPtr::create();
// Note: we assume 4 components per pixel and not compressed for now
dataPtr->setImage(m_image);
return dataPtr;
}
bool GLTFRawTextureImage::GLTFRawTextureImageFunctor::operator ==(const QTextureImageDataGenerator &other) const
{
const GLTFRawTextureImageFunctor *otherFunctor = functor_cast<GLTFRawTextureImageFunctor>(&other);
return (otherFunctor != nullptr && otherFunctor->m_image == m_image);
}
} // namespace Qt3DRender
QT_END_NAMESPACE
#include "gltfimporter.moc"