/**************************************************************************** ** ** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the Qt3D module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL3$ ** 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 http://www.qt.io/terms-conditions. For further ** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free ** Software Foundation and appearing in the file LICENSE.GPL included in ** the packaging of this file. Please review the following information to ** ensure the GNU General Public License version 2.0 requirements will be ** met: http://www.gnu.org/licenses/gpl-2.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "loadskeletonjob_p.h" #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE namespace Qt3DRender { namespace Render { class LoadSkeletonJobPrivate : public Qt3DCore::QAspectJobPrivate { public: LoadSkeletonJobPrivate() : m_backendSkeleton(nullptr), m_loadedRootJoint(nullptr) { } ~LoadSkeletonJobPrivate() override { } void postFrame(Qt3DCore::QAspectManager *manager) override; Skeleton *m_backendSkeleton; Qt3DCore::QJoint* m_loadedRootJoint; }; LoadSkeletonJob::LoadSkeletonJob(const HSkeleton &handle) : QAspectJob(*new LoadSkeletonJobPrivate) , m_handle(handle) , m_nodeManagers(nullptr) { SET_JOB_RUN_STAT_TYPE(this, JobTypes::LoadSkeleton, 0) } void LoadSkeletonJob::run() { Q_DJOB(LoadSkeletonJob); d->m_backendSkeleton = nullptr; Skeleton *skeleton = m_nodeManagers->skeletonManager()->data(m_handle); if (skeleton != nullptr) { d->m_backendSkeleton = skeleton; loadSkeleton(skeleton); } } void LoadSkeletonJob::loadSkeleton(Skeleton *skeleton) { qCDebug(Jobs) << Q_FUNC_INFO << skeleton->source(); skeleton->clearData(); // Load the data switch (skeleton->dataType()) { case Skeleton::File: loadSkeletonFromUrl(skeleton); break; case Skeleton::Data: loadSkeletonFromData(skeleton); break; default: Q_UNREACHABLE(); } // If using a loader inform the frontend of the status change. // Don't bother if asked to create frontend joints though. When // the backend gets notified of those joints we'll update the // status at that point. if (skeleton->dataType() == Skeleton::File && !skeleton->createJoints()) { if (skeleton->jointCount() == 0) skeleton->setStatus(Qt3DCore::QSkeletonLoader::Error); else skeleton->setStatus(Qt3DCore::QSkeletonLoader::Ready); } qCDebug(Jobs) << "Loaded skeleton data:" << *skeleton; } void LoadSkeletonJob::loadSkeletonFromUrl(Skeleton *skeleton) { Q_DJOB(LoadSkeletonJob); using namespace Qt3DCore; // TODO: Handle remote files QString filePath = Qt3DRender::QUrlHelper::urlToLocalFileOrQrc(skeleton->source()); QFileInfo info(filePath); if (!info.exists()) { qWarning() << "Could not open skeleton file:" << filePath; skeleton->setStatus(Qt3DCore::QSkeletonLoader::Error); return; } QFile file(filePath); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "Could not open skeleton file:" << filePath; skeleton->setStatus(QSkeletonLoader::Error); return; } // TODO: Make plugin based for more file type support. For now gltf or native const QString ext = info.suffix(); SkeletonData skeletonData; if (ext == QLatin1String("gltf")) { GLTFSkeletonLoader loader; loader.load(&file); skeletonData = loader.createSkeleton(skeleton->name()); // If the user has requested it, create the frontend nodes for the joints // and send them to the (soon to be owning) QSkeletonLoader. if (skeleton->createJoints()) { QJoint *rootJoint = createFrontendJoints(skeletonData); if (!rootJoint) { qWarning() << "Failed to create frontend joints"; skeleton->setStatus(QSkeletonLoader::Error); return; } // Move the QJoint tree to the main thread and notify the // corresponding QSkeletonLoader const auto appThread = QCoreApplication::instance()->thread(); rootJoint->moveToThread(appThread); d->m_loadedRootJoint = rootJoint; // Clear the skeleton data. It will be recreated from the // frontend joints. A little bit inefficient but ensures // that joints created this way and via QSkeleton go through // the same code path. skeletonData = SkeletonData(); } } else if (ext == QLatin1String("json")) { // TODO: Support native skeleton type } else { qWarning() << "Unknown skeleton file type:" << ext; skeleton->setStatus(QSkeletonLoader::Error); return; } skeleton->setSkeletonData(skeletonData); } void LoadSkeletonJob::loadSkeletonFromData(Skeleton *skeleton) { // Recurse down through the joint hierarchy and process it into // the vector of joints used within SkeletonData. The recursion // ensures that a parent always appears before its children in // the vector of JointInfo objects. // // In addition, we set up a mapping from the joint ids to the // index of the corresponding JointInfo object in the vector. // This will allow us to easily update entries in the vector of // JointInfos when a Joint node marks itself as dirty. const int rootParentIndex = -1; auto skeletonData = skeleton->skeletonData(); processJointHierarchy(skeleton->rootJointId(), rootParentIndex, skeletonData); skeleton->setSkeletonData(skeletonData); } Qt3DCore::QJoint *LoadSkeletonJob::createFrontendJoints(const SkeletonData &skeletonData) const { if (skeletonData.joints.isEmpty()) return nullptr; // Create frontend joints from the joint info objects QVector frontendJoints; const int jointCount = skeletonData.joints.size(); frontendJoints.reserve(jointCount); for (int i = 0; i < jointCount; ++i) { const QMatrix4x4 &inverseBindMatrix = skeletonData.joints[i].inverseBindPose; const QString &jointName = skeletonData.jointNames[i]; const Qt3DCore::Sqt &localPose = skeletonData.localPoses[i]; frontendJoints.push_back(createFrontendJoint(jointName, localPose, inverseBindMatrix)); } // Now go through and resolve the parent for each joint for (int i = 0; i < frontendJoints.size(); ++i) { const auto parentIndex = skeletonData.joints[i].parentIndex; if (parentIndex == -1) continue; // It's not enough to just set up the QObject parent-child relationship. // We need to explicitly add the child to the parent's list of joints so // that information is then propagated to the backend. frontendJoints[parentIndex]->addChildJoint(frontendJoints[i]); } return frontendJoints[0]; } Qt3DCore::QJoint *LoadSkeletonJob::createFrontendJoint(const QString &jointName, const Qt3DCore::Sqt &localPose, const QMatrix4x4 &inverseBindMatrix) const { auto joint = Qt3DCore::QAbstractNodeFactory::createNode("QJoint"); joint->setTranslation(localPose.translation); joint->setRotation(localPose.rotation); joint->setScale(localPose.scale); joint->setInverseBindMatrix(inverseBindMatrix); joint->setName(jointName); return joint; } void LoadSkeletonJob::processJointHierarchy(Qt3DCore::QNodeId jointId, int parentJointIndex, SkeletonData &skeletonData) { // Lookup the joint, create a JointInfo, and add an entry to the index map Joint *joint = m_nodeManagers->jointManager()->lookupResource(jointId); Q_ASSERT(joint); joint->setOwningSkeleton(m_handle); const JointInfo jointInfo(joint, parentJointIndex); skeletonData.joints.push_back(jointInfo); skeletonData.localPoses.push_back(joint->localPose()); skeletonData.jointNames.push_back(joint->name()); const int jointIndex = skeletonData.joints.size() - 1; const HJoint jointHandle = m_nodeManagers->jointManager()->lookupHandle(jointId); skeletonData.jointIndices.insert(jointHandle, jointIndex); // Recurse to the children const auto childIds = joint->childJointIds(); for (const auto &childJointId : childIds) processJointHierarchy(childJointId, jointIndex, skeletonData); } void LoadSkeletonJobPrivate::postFrame(Qt3DCore::QAspectManager *manager) { if (!m_backendSkeleton) return; using namespace Qt3DCore; QAbstractSkeleton *node = qobject_cast(manager->lookupNode(m_backendSkeleton->peerId())); if (!node) return; QAbstractSkeletonPrivate *dnode = QAbstractSkeletonPrivate::get(node); dnode->m_jointCount = m_backendSkeleton->jointCount(); dnode->m_jointNames = m_backendSkeleton->jointNames(); dnode->m_localPoses = m_backendSkeleton->localPoses(); dnode->update(); QSkeletonLoader *loaderNode = qobject_cast(node); if (loaderNode) { QSkeletonLoaderPrivate *dloaderNode = static_cast(QSkeletonLoaderPrivate::get(loaderNode)); dloaderNode->setStatus(m_backendSkeleton->status()); if (m_loadedRootJoint) { dloaderNode->setRootJoint(m_loadedRootJoint); m_loadedRootJoint = nullptr; } } } } // namespace Render } // namespace Qt3DRender QT_END_NAMESPACE