| /**************************************************************************** |
| ** |
| ** 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 "fbxgeometryloader.h" |
| |
| #include <QtCore/QFileDevice> |
| |
| #include <Qt3DRender/QAttribute> |
| #include <Qt3DRender/QBuffer> |
| #include <Qt3DRender/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 |