Revision control

++ // Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "fbxgeometryloader.h"
++
++ #include <QtCore/QFileDevice>
++
++ #include <Qt3DCore/QAttribute>
++ #include <Qt3DCore/QBuffer>
++ #include <Qt3DCore/QGeometry>
++ #include <Qt3DRender/private/renderlogging_p.h>
++
++ #include <fbxsdk.h>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace Qt3DRender {
++
++ Q_LOGGING_CATEGORY(FbxGeometryLoaderLog, "Qt3D.FbxGeometryLoader", QtWarningMsg)
++
++ class FbxStreamWrapper : public FbxStream
++ {
++ FbxManager *m_manager;
++ QIODevice *m_device;
++ public:
++ FbxStreamWrapper(FbxManager *manager)
++ : m_manager(manager)
++ , m_device(nullptr)
++ {
++ }
++
++ EState GetState()
++ {
++ if (!m_device)
++ return eEmpty;
++ return m_device->isOpen() ? eOpen : eClosed;
++ }
++
++ bool Open(void *pStreamData)
++ {
++ m_device = reinterpret_cast<QIODevice *>(pStreamData);
++
++ if (!m_device->isOpen())
++ m_device->open(QIODevice::ReadOnly);
++
++ return m_device && m_device->isOpen();
++ }
++
++ bool Close()
++ {
++ Q_ASSERT(m_device);
++
++ if (Q_UNLIKELY(!m_device))
++ return false;
++
++ m_device->close();
++
++ return !m_device->isOpen();
++ }
++
++ bool Flush()
++ {
++ Q_ASSERT(m_device);
++
++ if (Q_UNLIKELY(!m_device))
++ return false;
++
++ QFileDevice *device = qobject_cast<QFileDevice*>(m_device);
++ if (Q_LIKELY(device))
++ return device->flush();
++
++ return false;
++ }
++
++ int Write(const void *pData, int pSize)
++ {
++ Q_ASSERT(m_device);
++
++ if (Q_UNLIKELY(!m_device))
++ return -1;
++
++ return m_device->write(reinterpret_cast<const char *>(pData), pSize);
++ }
++
++ int Read(void *pData, int pSize) const
++ {
++ Q_ASSERT(m_device);
++
++ if (Q_UNLIKELY(!m_device))
++ return -1;
++
++ return m_device->read(reinterpret_cast<char *>(pData), pSize);
++ }
++
++ char *ReadString(char *pBuffer, int pMaxSize, bool pStopAtFirstWhiteSpace = false)
++ {
++ Q_UNUSED(pBuffer);
++ Q_UNUSED(pMaxSize);
++ Q_UNUSED(pStopAtFirstWhiteSpace);
++ return nullptr;
++ }
++
++ int GetReaderID() const
++ {
++ return m_manager->GetIOPluginRegistry()->
++ FindReaderIDByExtension(qPrintable(FBXGEOMETRYLOADER_EXT));
++ }
++
++ int GetWriterID() const
++ {
++ return m_manager->GetIOPluginRegistry()->
++ FindReaderIDByExtension(qPrintable(FBXGEOMETRYLOADER_EXT));
++ }
++
++ void Seek(const FbxInt64 &pOffset, const FbxFile::ESeekPos &pSeekPos)
++ {
++ Q_ASSERT(m_device);
++
++ if (Q_UNLIKELY(!m_device))
++ return;
++
++ switch (pSeekPos) {
++ case FbxFile::eBegin:
++ m_device->seek(pOffset);
++ break;
++ case FbxFile::eCurrent:
++ m_device->seek(m_device->pos() + pOffset);
++ break;
++ case FbxFile::eEnd:
++ m_device->seek(m_device->size() - pOffset);
++ break;
++ }
++ }
++
++ long GetPosition() const
++ {
++ Q_ASSERT(m_device);
++
++ if (Q_UNLIKELY(!m_device))
++ return -1;
++
++ return m_device->pos();
++ }
++
++ void SetPosition(long pPosition)
++ {
++ Q_ASSERT(m_device);
++
++ if (Q_UNLIKELY(!m_device))
++ return;
++
++ m_device->seek(pPosition);
++ }
++
++ int GetError() const
++ {
++ Q_ASSERT(m_device);
++
++ if (Q_UNLIKELY(!m_device))
++ return 1;
++
++ QFileDevice *device = qobject_cast<QFileDevice *>(m_device);
++ if (Q_LIKELY(device))
++ return device->error() == QFileDevice::NoError ? 0 : 1;
++
++ return 0;
++ }
++
++ void ClearError()
++ {
++ Q_ASSERT(m_device);
++
++ if (Q_UNLIKELY(!m_device))
++ return;
++
++ QFileDevice *device = qobject_cast<QFileDevice *>(m_device);
++ if (Q_LIKELY(device))
++ device->unsetError();
++ }
++ };
++
++ FbxGeometryLoader::FbxGeometryLoader()
++ : m_manager(nullptr)
++ , m_scene(nullptr)
++ , m_geometry(nullptr)
++ {
++ m_manager = FbxManager::Create();
++
++ if (Q_LIKELY(m_manager))
++ qInfo(FbxGeometryLoaderLog, "Autodesk FBX SDK version %s", m_manager->GetVersion());
++ else
++ qWarning(FbxGeometryLoaderLog, "Failed to create FBX Manager.");
++ }
++
++ FbxGeometryLoader::~FbxGeometryLoader()
++ {
++ if (m_manager)
++ m_manager->Destroy();
++ }
++
++ /*!
++ Gives the list of attributes that can be used to render
++ a 3D form.
++ Returns a pointer to the geometry object.
++ */
++ QGeometry *Qt3DRender::FbxGeometryLoader::geometry() const
++ {
++ return m_geometry;
++ }
++
++ /*!
++ Loads the specified \a subMesh using a device \a ioDev. Returns \c true
++ on success, \c false otherwise.
++ */
++ bool Qt3DRender::FbxGeometryLoader::load(QIODevice *ioDev, const QString &subMesh)
++ {
++ if (m_scene)
++ m_scene->Destroy();
++
++ m_scene = FbxScene::Create(m_manager, "scene");
++ if (!m_scene)
++ qWarning(FbxGeometryLoaderLog, "Unable to create FBX scene!");
++
++ QScopedPointer<FbxStreamWrapper> fbxStream(new FbxStreamWrapper(m_manager));
++
++ FbxImporter *importer = FbxImporter::Create(m_manager, "");
++
++ const bool hasInitialized = importer->Initialize(fbxStream.data(), ioDev);
++
++ int fileVersion[3];
++
++ importer->GetFileVersion(fileVersion[0], fileVersion[1], fileVersion[2]);
++
++ if (!hasInitialized) {
++ FbxString error = importer->GetStatus().GetErrorString();
++ qWarning(FbxGeometryLoaderLog, "Call to FbxImporter::Initialize() failed.");
++ qWarning(FbxGeometryLoaderLog, "Error returned: %s", error.Buffer());
++
++ if (importer->GetStatus().GetCode() == FbxStatus::eInvalidFileVersion) {
++ int SdkVersion[3];
++ FbxManager::GetFileFormatVersion(SdkVersion[0], SdkVersion[1], SdkVersion[2]);
++ qWarning(FbxGeometryLoaderLog, "FBX file format version for this FBX SDK is %d.%d.%d",
++ SdkVersion[0], SdkVersion[1], SdkVersion[2]);
++ qWarning(FbxGeometryLoaderLog, "FBX file format version for the file is %d.%d.%d",
++ fileVersion[0], fileVersion[1], fileVersion[2]);
++ }
++
++ return false;
++ }
++
++ if (importer->IsFBX()) {
++ qInfo(FbxGeometryLoaderLog, "FBX file format version for file is %d.%d.%d",
++ fileVersion[0], fileVersion[1], fileVersion[2]);
++
++ const int stackCount = importer->GetAnimStackCount();
++ for (int i = 0; i < stackCount; ++i) {
++ FbxTakeInfo *lTakeInfo = importer->GetTakeInfo(i);
++ qInfo(FbxGeometryLoaderLog, " Animation Stack %d", i);
++ qInfo(FbxGeometryLoaderLog, " Name: \"%s\"", lTakeInfo->mName.Buffer());
++ qInfo(FbxGeometryLoaderLog, " Description: \"%s\"", lTakeInfo->mDescription.Buffer());
++ qInfo(FbxGeometryLoaderLog, " Import Name: \"%s\"", lTakeInfo->mImportName.Buffer());
++ qInfo(FbxGeometryLoaderLog, " Import State: %s", lTakeInfo->mSelect ? "true" : "false");
++ }
++
++ auto settings = importer->GetIOSettings();
++ settings->SetBoolProp(IMP_FBX_MATERIAL, false);
++ settings->SetBoolProp(IMP_FBX_TEXTURE, false);
++ settings->SetBoolProp(IMP_FBX_LINK, false);
++ settings->SetBoolProp(IMP_FBX_SHAPE, false);
++ settings->SetBoolProp(IMP_FBX_GOBO, false);
++ settings->SetBoolProp(IMP_FBX_ANIMATION, false);
++ settings->SetBoolProp(IMP_FBX_GLOBAL_SETTINGS, false);
++ }
++
++ const bool wasImported = importer->Import(m_scene);
++ importer->Destroy();
++
++ m_mesh = subMesh;
++
++ if (wasImported)
++ recurseNodes();
++
++ return wasImported;
++ }
++
++ /*!
++ Traverse the node hierarchy and process the children of each
++ node.
++ */
++ void FbxGeometryLoader::recurseNodes()
++ {
++ Q_ASSERT(m_scene);
++
++ if (!m_scene)
++ return;
++
++ FbxNode *node = m_scene->GetRootNode();
++ if (node) {
++ for (int i = 0; i < node->GetChildCount() && !m_geometry; ++i)
++ processNode(node->GetChild(i));
++ }
++ }
++ /*!
++ If the parameter \a node has the attribute eMesh,
++ process the Mesh. If not, process the children
++ of that node.
++ */
++ void Qt3DRender::FbxGeometryLoader::processNode(FbxNode *node)
++ {
++ auto attr = node->GetNodeAttribute();
++ if (!attr)
++ return;
++
++ switch (attr->GetAttributeType()) {
++ case FbxNodeAttribute::eMesh:
++ if (m_mesh.isEmpty() || m_mesh.compare(node->GetName(), Qt::CaseInsensitive) == 0) {
++ qDebug(FbxGeometryLoaderLog, "Found mesh: %s", node->GetName());
++ processMesh(node->GetMesh());
++ }
++ break;
++ default:
++ break;
++ }
++
++ if (m_geometry)
++ return;
++
++ for (int i = 0; i < node->GetChildCount(); ++i)
++ processNode(node->GetChild(i));
++ }
++ /*!
++ Process all vertices of the specified \a mesh.
++ */
++ void Qt3DRender::FbxGeometryLoader::processMesh(FbxMesh *mesh)
++ {
++ const int normalCount = mesh->GetElementNormalCount();
++ const int polygonCount = mesh->GetPolygonCount();
++ const int tangentCount = mesh->GetElementTangentCount();
++ const int uvCount = mesh->GetElementUVCount();
++
++ const bool hasNormal = (normalCount > 0);
++ const bool hasTangent = (tangentCount > 0);
++ const bool hasUV = (uvCount > 0);
++
++ const unsigned int elementSize = 3 + (hasUV ? 2 : 0) + (hasNormal ? 3 : 0) + (hasTangent ? 4 : 0);
++ const unsigned int elementBytes = elementSize * sizeof(double);
++
++ int vertexCount = 0;
++ for (int polygonIndex = 0; polygonIndex < polygonCount; ++polygonIndex)
++ vertexCount += mesh->GetPolygonSize(polygonIndex);
++ const int indexCount = (polygonCount * 3) + ((vertexCount - (polygonCount * 3)) * 3);
++
++ QByteArray indexPayload;
++ indexPayload.resize(indexCount * sizeof(quint32));
++ quint32 *indexData = reinterpret_cast<quint32 *>(indexPayload.data());
++
++ QByteArray vertexPayload;
++ vertexPayload.resize(vertexCount * elementBytes);
++ double *vertexData = reinterpret_cast<double *>(vertexPayload.data());
++
++ const FbxVector4 *controlPoints = mesh->GetControlPoints();
++
++ int vertexIndex = 0;
++ for (int polygonIndex = 0; polygonIndex < polygonCount; ++polygonIndex) {
++ const int polygonSize = mesh->GetPolygonSize(polygonIndex);
++ for (int pVertexIndex = 0; pVertexIndex < polygonSize; ++pVertexIndex) {
++ const int controlPointIndex = mesh->GetPolygonVertex(polygonIndex, pVertexIndex);
++ const FbxVector4 *vertex = (controlPoints + controlPointIndex);
++ *vertexData++ = (*vertex)[0];
++ *vertexData++ = (*vertex)[1];
++ *vertexData++ = (*vertex)[2];
++
++ if (pVertexIndex >= 1 && pVertexIndex < (polygonSize - 1)) {
++ *indexData++ = vertexIndex - pVertexIndex;
++ *indexData++ = vertexIndex;
++ *indexData++ = vertexIndex + 1;
++ }
++
++ if (hasUV) {
++ FbxVector2 vector;
++ bool unmapped;
++
++ if (mesh->GetPolygonVertexUV(polygonIndex, pVertexIndex, NULL, vector, unmapped)) {
++ *vertexData++ = unmapped ? 0 : vector[0];
++ *vertexData++ = unmapped ? 0 : vector[1];
++ } else {
++ qWarning(FbxGeometryLoaderLog,
++ "Irregularity encountered while parsing UV element.");
++ *vertexData++ = 0;
++ *vertexData++ = 0;
++ }
++ }
++
++ if (hasNormal) {
++ FbxVector4 vector;
++ if (mesh->GetPolygonVertexNormal(polygonIndex, pVertexIndex, vector)) {
++ *vertexData++ = vector[0];
++ *vertexData++ = vector[1];
++ *vertexData++ = vector[2];
++ } else {
++ qWarning(FbxGeometryLoaderLog,
++ "Irregularity encountered while parsing Normal element.");
++ *vertexData++ = 0;
++ *vertexData++ = 0;
++ *vertexData++ = 0;
++ }
++ }
++
++ if (hasTangent) {
++ int index = -1;
++
++ for (int tangentIndex = 0; tangentIndex < tangentCount && index == -1; ++tangentIndex) {
++ const FbxGeometryElementTangent *tangent = mesh->GetElementTangent(tangentIndex);
++
++ if (tangent->GetMappingMode() == FbxGeometryElement::eByPolygonVertex) {
++ switch (tangent->GetReferenceMode()) {
++ case FbxGeometryElement::eDirect:
++ index = vertexIndex;
++ break;
++ case FbxGeometryElement::eIndexToDirect:
++ index = tangent->GetIndexArray().GetAt(vertexIndex);
++ break;
++ default: break;
++ }
++ }
++
++ if (index != -1) {
++ const FbxVector4 vector = tangent->GetDirectArray().GetAt(index);
++ *vertexData++ = vector[0];
++ *vertexData++ = vector[1];
++ *vertexData++ = vector[2];
++ *vertexData++ = vector[3];
++ }
++ }
++
++ if (index == -1) {
++ qWarning(FbxGeometryLoaderLog,
++ "Irregularity encountered while parsing Tangent element.");
++ *vertexData++ = 0;
++ *vertexData++ = 0;
++ *vertexData++ = 0;
++ *vertexData++ = 0;
++ }
++ }
++ ++vertexIndex;
++ }
++ }
++
++ /*
++ * QGeometry Generation
++ */
++ m_geometry = new QGeometry();
++
++ unsigned int offset = 0;
++
++ QBuffer *vertexBuffer = new QBuffer(QBuffer::VertexBuffer);
++ vertexBuffer->setData(vertexPayload);
++
++ QBuffer *indexBuffer = new QBuffer(QBuffer::IndexBuffer);
++ indexBuffer->setData(indexPayload);
++
++ QAttribute *positionAttribute = new QAttribute(vertexBuffer, QAttribute::defaultPositionAttributeName(), QAttribute::Double, 3, vertexCount, offset, elementBytes);
++ m_geometry->addAttribute(positionAttribute);
++ offset += sizeof(double) * 3;
++
++ if (hasUV) {
++ QAttribute *attribute = new QAttribute(vertexBuffer, QAttribute::defaultTextureCoordinateAttributeName(), QAttribute::Double, 2, vertexCount, offset, elementBytes);
++ m_geometry->addAttribute(attribute);
++ offset += sizeof(double) * 2;
++ }
++
++ if (hasNormal) {
++ QAttribute *attribute = new QAttribute(vertexBuffer, QAttribute::defaultNormalAttributeName(), QAttribute::Double, 3, vertexCount, offset, elementBytes);
++ m_geometry->addAttribute(attribute);
++ offset += sizeof(double) * 3;
++ }
++
++ if (hasTangent) {
++ QAttribute *attribute = new QAttribute(vertexBuffer, QAttribute::defaultTangentAttributeName(),QAttribute::Double, 4, vertexCount, offset, elementBytes);
++ m_geometry->addAttribute(attribute);
++ }
++
++ QAttribute *indexAttribute = new QAttribute(indexBuffer, QAttribute::UnsignedInt, 1, indexCount);
++ indexAttribute->setAttributeType(QAttribute::IndexAttribute);
++ m_geometry->addAttribute(indexAttribute);
++ }
++
++ } // namespace Qt3DRender
++
++ QT_END_NAMESPACE