blob: 2c8bb14c3340f418fcc0b3b12ac0177acfa418b8 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
** 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 "gltfgeometryloader.h"
#include <QtCore/QDir>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonObject>
#include <QtCore/QVersionNumber>
#include <QOpenGLTexture>
#include <Qt3DRender/QGeometry>
#include <Qt3DRender/private/renderlogging_p.h>
#include <Qt3DCore/private/qloadgltf_p.h>
QT_BEGIN_NAMESPACE
#ifndef qUtf16PrintableImpl // -Impl is a Qt 5.8 feature
# define qUtf16PrintableImpl(string) \
static_cast<const wchar_t*>(static_cast<const void*>(string.utf16()))
#endif
namespace Qt3DRender {
Q_LOGGING_CATEGORY(GLTFGeometryLoaderLog, "Qt3D.GLTFGeometryLoader", QtWarningMsg)
#define KEY_ASSET QLatin1String("asset")
#define KEY_ACCESSORS QLatin1String("accessors")
#define KEY_ATTRIBUTES QLatin1String("attributes")
#define KEY_BUFFER QLatin1String("buffer")
#define KEY_BUFFERS QLatin1String("buffers")
#define KEY_BYTE_LENGTH QLatin1String("byteLength")
#define KEY_BYTE_OFFSET QLatin1String("byteOffset")
#define KEY_BYTE_STRIDE QLatin1String("byteStride")
#define KEY_COUNT QLatin1String("count")
#define KEY_INDICES QLatin1String("indices")
#define KEY_MATERIAL QLatin1String("material")
#define KEY_MESHES QLatin1String("meshes")
#define KEY_NAME QLatin1String("name")
#define KEY_PRIMITIVES QLatin1String("primitives")
#define KEY_TARGET QLatin1String("target")
#define KEY_TYPE QLatin1String("type")
#define KEY_URI QLatin1String("uri")
#define KEY_VERSION QLatin1String("version")
#define KEY_BUFFER_VIEW QLatin1String("bufferView")
#define KEY_BUFFER_VIEWS QLatin1String("bufferViews")
#define KEY_COMPONENT_TYPE QLatin1String("componentType")
GLTFGeometryLoader::GLTFGeometryLoader()
: m_geometry(nullptr)
{
}
GLTFGeometryLoader::~GLTFGeometryLoader()
{
cleanup();
}
QGeometry *GLTFGeometryLoader::geometry() const
{
return m_geometry;
}
bool GLTFGeometryLoader::load(QIODevice *ioDev, const QString &subMesh)
{
Q_UNUSED(subMesh);
if (Q_UNLIKELY(!setJSON(qLoadGLTF(ioDev->readAll())))) {
qCWarning(GLTFGeometryLoaderLog, "not a JSON document");
return false;
}
auto file = qobject_cast<QFile*>(ioDev);
if (file) {
QFileInfo finfo(file->fileName());
setBasePath(finfo.dir().absolutePath());
}
m_mesh = subMesh;
parse();
return true;
}
GLTFGeometryLoader::BufferData::BufferData()
: length(0)
, data(nullptr)
{
}
GLTFGeometryLoader::BufferData::BufferData(const QJsonObject &json)
: length(json.value(KEY_BYTE_LENGTH).toInt())
, path(json.value(KEY_URI).toString())
, data(nullptr)
{
}
GLTFGeometryLoader::AccessorData::AccessorData()
: bufferViewIndex(0)
, type(QAttribute::Float)
, dataSize(0)
, count(0)
, offset(0)
, stride(0)
{
}
GLTFGeometryLoader::AccessorData::AccessorData(const QJsonObject &json)
: bufferViewName(json.value(KEY_BUFFER_VIEW).toString())
, bufferViewIndex(json.value(KEY_BUFFER_VIEW).toInt(-1))
, 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)
{
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();
}
void GLTFGeometryLoader::setBasePath(const QString &path)
{
m_basePath = path;
}
bool GLTFGeometryLoader::setJSON(const QJsonDocument &json )
{
if (!json.isObject())
return false;
m_json = json;
cleanup();
return true;
}
QString GLTFGeometryLoader::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("JOINTS")))
return QAttribute::defaultJointIndicesAttributeName();
if (semantic.startsWith(QLatin1String("WEIGHTS")))
return QAttribute::defaultJointWeightsAttributeName();
return QString();
}
void GLTFGeometryLoader::parse()
{
// Find the glTF version
const QJsonObject asset = m_json.object().value(KEY_ASSET).toObject();
const QString versionString = asset.value(KEY_VERSION).toString();
const auto version = QVersionNumber::fromString(versionString);
switch (version.majorVersion()) {
case 1:
parseGLTF1();
break;
case 2:
parseGLTF2();
break;
default:
qWarning() << "Unsupported version of glTF" << versionString;
}
}
void GLTFGeometryLoader::parseGLTF1()
{
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 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 && !m_geometry; ++it) {
const QJsonObject &mesh = it.value().toObject();
if (m_mesh.isEmpty() ||
m_mesh.compare(mesh.value(KEY_NAME).toString(), Qt::CaseInsensitive) == 0)
processJSONMesh(it.key(), mesh);
}
}
void GLTFGeometryLoader::parseGLTF2()
{
const QJsonArray buffers = m_json.object().value(KEY_BUFFERS).toArray();
for (auto it = buffers.begin(), end = buffers.end(); it != end; ++it)
processJSONBufferV2(it->toObject());
const QJsonArray views = m_json.object().value(KEY_BUFFER_VIEWS).toArray();
loadBufferDataV2();
for (auto it = views.begin(), end = views.end(); it != end; ++it)
processJSONBufferViewV2(it->toObject());
unloadBufferDataV2();
const QJsonArray attrs = m_json.object().value(KEY_ACCESSORS).toArray();
for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it)
processJSONAccessorV2(it->toObject());
const QJsonArray meshes = m_json.object().value(KEY_MESHES).toArray();
for (auto it = meshes.begin(), end = meshes.end(); it != end && !m_geometry; ++it) {
const QJsonObject &mesh = it->toObject();
if (m_mesh.isEmpty() || m_mesh.compare(mesh.value(KEY_NAME).toString(), Qt::CaseInsensitive) == 0)
processJSONMeshV2(mesh);
}
}
void GLTFGeometryLoader::cleanup()
{
m_geometry = nullptr;
m_gltf1.m_accessorDict.clear();
m_gltf1.m_buffers.clear();
}
void GLTFGeometryLoader::processJSONBuffer(const QString &id, const QJsonObject &json)
{
// simply cache buffers for lookup by buffer-views
m_gltf1.m_bufferDatas[id] = BufferData(json);
}
void GLTFGeometryLoader::processJSONBufferV2(const QJsonObject &json)
{
// simply cache buffers for lookup by buffer-views
m_gltf2.m_bufferDatas.push_back(BufferData(json));
}
void GLTFGeometryLoader::processJSONBufferView(const QString &id, const QJsonObject &json)
{
QString bufName = json.value(KEY_BUFFER).toString();
const auto it = qAsConst(m_gltf1.m_bufferDatas).find(bufName);
if (Q_UNLIKELY(it == m_gltf1.m_bufferDatas.cend())) {
qCWarning(GLTFGeometryLoaderLog, "unknown buffer: %ls processing view: %ls",
qUtf16PrintableImpl(bufName), qUtf16PrintableImpl(id));
return;
}
const auto &bufferData = *it;
int target = json.value(KEY_TARGET).toInt();
switch (target) {
case GL_ARRAY_BUFFER:
case GL_ELEMENT_ARRAY_BUFFER:
break;
default:
qCWarning(GLTFGeometryLoaderLog, "buffer %ls unsupported target: %d",
qUtf16PrintableImpl(id), target);
return;
}
quint64 offset = 0;
const auto byteOffset = json.value(KEY_BYTE_OFFSET);
if (!byteOffset.isUndefined()) {
offset = byteOffset.toInt();
qCDebug(GLTFGeometryLoaderLog, "bv: %ls has offset: %lld", qUtf16PrintableImpl(id), offset);
}
const quint64 len = json.value(KEY_BYTE_LENGTH).toInt();
QByteArray bytes = bufferData.data->mid(offset, len);
if (Q_UNLIKELY(bytes.count() != int(len))) {
qCWarning(GLTFGeometryLoaderLog, "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_gltf1.m_buffers[id] = b;
}
void GLTFGeometryLoader::processJSONBufferViewV2(const QJsonObject &json)
{
const int bufferIndex = json.value(KEY_BUFFER).toInt();
if (Q_UNLIKELY(bufferIndex) >= m_gltf2.m_bufferDatas.size()) {
qCWarning(GLTFGeometryLoaderLog, "unknown buffer: %d processing view", bufferIndex);
return;
}
const auto bufferData = m_gltf2.m_bufferDatas[bufferIndex];
int target = json.value(KEY_TARGET).toInt();
switch (target) {
case GL_ARRAY_BUFFER:
case GL_ELEMENT_ARRAY_BUFFER:
break;
default:
return;
}
quint64 offset = 0;
const auto byteOffset = json.value(KEY_BYTE_OFFSET);
if (!byteOffset.isUndefined()) {
offset = byteOffset.toInt();
qCDebug(GLTFGeometryLoaderLog, "bufferview has offset: %lld", offset);
}
const quint64 len = json.value(KEY_BYTE_LENGTH).toInt();
QByteArray bytes = bufferData.data->mid(offset, len);
if (Q_UNLIKELY(bytes.count() != int(len))) {
qCWarning(GLTFGeometryLoaderLog, "failed to read sufficient bytes from: %ls for view",
qUtf16PrintableImpl(bufferData.path));
}
auto b = new Qt3DRender::QBuffer;
b->setData(bytes);
m_gltf2.m_buffers.push_back(b);
}
void GLTFGeometryLoader::processJSONAccessor(const QString &id, const QJsonObject &json)
{
m_gltf1.m_accessorDict[id] = AccessorData(json);
}
void GLTFGeometryLoader::processJSONAccessorV2(const QJsonObject &json)
{
m_gltf2.m_accessors.push_back(AccessorData(json));
}
void GLTFGeometryLoader::processJSONMesh(const QString &id, const QJsonObject &json)
{
const QJsonArray primitivesArray = json.value(KEY_PRIMITIVES).toArray();
for (const QJsonValue &primitiveValue : primitivesArray) {
QJsonObject primitiveObject = primitiveValue.toObject();
QString material = primitiveObject.value(KEY_MATERIAL).toString();
if (Q_UNLIKELY(material.isEmpty())) {
qCWarning(GLTFGeometryLoaderLog, "malformed primitive on %ls, missing material value %ls",
qUtf16PrintableImpl(id), qUtf16PrintableImpl(material));
continue;
}
QGeometry *meshGeometry = new QGeometry;
const QJsonObject attrs = primitiveObject.value(KEY_ATTRIBUTES).toObject();
for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) {
QString k = it.value().toString();
const auto accessorIt = qAsConst(m_gltf1.m_accessorDict).find(k);
if (Q_UNLIKELY(accessorIt == m_gltf1.m_accessorDict.cend())) {
qCWarning(GLTFGeometryLoaderLog, "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_gltf1.m_buffers.value(accessorIt->bufferViewName, nullptr);
if (Q_UNLIKELY(!buffer)) {
qCWarning(GLTFGeometryLoaderLog, "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()) {
QString k = indices.toString();
const auto accessorIt = qAsConst(m_gltf1.m_accessorDict).find(k);
if (Q_UNLIKELY(accessorIt == m_gltf1.m_accessorDict.cend())) {
qCWarning(GLTFGeometryLoaderLog, "unknown index accessor: %ls on mesh %ls",
qUtf16PrintableImpl(k), qUtf16PrintableImpl(id));
} else {
//Get buffer handle for accessor
Qt3DRender::QBuffer *buffer = m_gltf1.m_buffers.value(accessorIt->bufferViewName, nullptr);
if (Q_UNLIKELY(!buffer)) {
qCWarning(GLTFGeometryLoaderLog, "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
m_geometry = meshGeometry;
break;
} // of primitives iteration
}
void GLTFGeometryLoader::processJSONMeshV2(const QJsonObject &json)
{
const QJsonArray primitivesArray = json.value(KEY_PRIMITIVES).toArray();
for (const QJsonValue &primitiveValue : primitivesArray) {
QJsonObject primitiveObject = primitiveValue.toObject();
QGeometry *meshGeometry = new QGeometry;
const QJsonObject attrs = primitiveObject.value(KEY_ATTRIBUTES).toObject();
for (auto it = attrs.begin(), end = attrs.end(); it != end; ++it) {
const int accessorIndex = it.value().toInt();
if (Q_UNLIKELY(accessorIndex >= m_gltf2.m_accessors.size())) {
qCWarning(GLTFGeometryLoaderLog, "unknown attribute accessor: %d on mesh %ls",
accessorIndex, qUtf16PrintableImpl(json.value(KEY_NAME).toString()));
continue;
}
const auto &accessor = m_gltf2.m_accessors[accessorIndex];
const QString attrName = it.key();
QString attributeName = standardAttributeNameFromSemantic(attrName);
if (attributeName.isEmpty())
attributeName = attrName;
// Get buffer handle for accessor
if (Q_UNLIKELY(accessor.bufferViewIndex >= m_gltf2.m_buffers.size())) {
qCWarning(GLTFGeometryLoaderLog, "unknown buffer-view: %d processing accessor: %ls",
accessor.bufferViewIndex, qUtf16PrintableImpl(json.value(KEY_NAME).toString()));
continue;
}
Qt3DRender::QBuffer *buffer = m_gltf2.m_buffers[accessor.bufferViewIndex];
QAttribute *attribute = new QAttribute(buffer,
attributeName,
accessor.type,
accessor.dataSize,
accessor.count,
accessor.offset,
accessor.stride);
attribute->setAttributeType(QAttribute::VertexAttribute);
meshGeometry->addAttribute(attribute);
}
const auto indices = primitiveObject.value(KEY_INDICES);
if (!indices.isUndefined()) {
const int accessorIndex = indices.toInt();
if (Q_UNLIKELY(accessorIndex >= m_gltf2.m_accessors.size())) {
qCWarning(GLTFGeometryLoaderLog, "unknown index accessor: %d on mesh %ls",
accessorIndex, qUtf16PrintableImpl(json.value(KEY_NAME).toString()));
} else {
const auto &accessor = m_gltf2.m_accessors[accessorIndex];
//Get buffer handle for accessor
if (Q_UNLIKELY(accessor.bufferViewIndex >= m_gltf2.m_buffers.size())) {
qCWarning(GLTFGeometryLoaderLog, "unknown buffer-view: %d processing accessor: %ls",
accessor.bufferViewIndex, qUtf16PrintableImpl(json.value(KEY_NAME).toString()));
continue;
}
Qt3DRender::QBuffer *buffer = m_gltf2.m_buffers[accessor.bufferViewIndex];
QAttribute *attribute = new QAttribute(buffer,
accessor.type,
accessor.dataSize,
accessor.count,
accessor.offset,
accessor.stride);
attribute->setAttributeType(QAttribute::IndexAttribute);
meshGeometry->addAttribute(attribute);
}
} // of has indices
m_geometry = meshGeometry;
break;
} // of primitives iteration
}
void GLTFGeometryLoader::loadBufferData()
{
for (auto &bufferData : m_gltf1.m_bufferDatas) {
if (!bufferData.data) {
bufferData.data = new QByteArray(resolveLocalData(bufferData.path));
}
}
}
void GLTFGeometryLoader::unloadBufferData()
{
for (const auto &bufferData : qAsConst(m_gltf1.m_bufferDatas)) {
QByteArray *data = bufferData.data;
delete data;
}
}
void GLTFGeometryLoader::loadBufferDataV2()
{
for (auto &bufferData : m_gltf2.m_bufferDatas) {
if (!bufferData.data)
bufferData.data = new QByteArray(resolveLocalData(bufferData.path));
}
}
void GLTFGeometryLoader::unloadBufferDataV2()
{
for (const auto &bufferData : qAsConst(m_gltf2.m_bufferDatas)) {
QByteArray *data = bufferData.data;
delete data;
}
}
QByteArray GLTFGeometryLoader::resolveLocalData(const QString &path) const
{
QDir d(m_basePath);
Q_ASSERT(d.exists());
QString absPath = d.absoluteFilePath(path);
QFile f(absPath);
f.open(QIODevice::ReadOnly);
return f.readAll();
}
QAttribute::VertexBaseType GLTFGeometryLoader::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(GLTFGeometryLoaderLog, "unsupported accessor type %d", componentType);
return QAttribute::Float;
}
uint GLTFGeometryLoader::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;
}
} // namespace Qt3DRender
QT_END_NAMESPACE