1// Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "loadskeletonjob_p.h"
5#include <Qt3DCore/qjoint.h>
6#include <Qt3DCore/qabstractskeleton.h>
7#include <Qt3DCore/qskeletonloader.h>
8#include <Qt3DCore/private/qabstractskeleton_p.h>
9#include <Qt3DCore/private/qabstractnodefactory_p.h>
10#include <Qt3DCore/private/qaspectmanager_p.h>
11#include <Qt3DCore/private/qskeletonloader_p.h>
12#include <Qt3DCore/private/qurlhelper_p.h>
13#include <Qt3DRender/private/managers_p.h>
14#include <Qt3DRender/private/nodemanagers_p.h>
15#include <Qt3DRender/private/job_common_p.h>
16#include <Qt3DRender/private/gltfskeletonloader_p.h>
17#include <Qt3DRender/private/renderlogging_p.h>
18#include <QtCore/QCoreApplication>
19#include <QtCore/QFileInfo>
20
21QT_BEGIN_NAMESPACE
22
23namespace Qt3DRender {
24namespace Render {
25
26class LoadSkeletonJobPrivate : public Qt3DCore::QAspectJobPrivate
27{
28public:
29 LoadSkeletonJobPrivate() : m_backendSkeleton(nullptr), m_loadedRootJoint(nullptr) { }
30 ~LoadSkeletonJobPrivate() override { }
31
32 void postFrame(Qt3DCore::QAspectManager *manager) override;
33
34 Skeleton *m_backendSkeleton;
35 Qt3DCore::QJoint* m_loadedRootJoint;
36};
37
38LoadSkeletonJob::LoadSkeletonJob(const HSkeleton &handle)
39 : QAspectJob(*new LoadSkeletonJobPrivate)
40 , m_handle(handle)
41 , m_nodeManagers(nullptr)
42{
43 SET_JOB_RUN_STAT_TYPE(this, JobTypes::LoadSkeleton, 0)
44}
45
46void LoadSkeletonJob::run()
47{
48 Q_D(LoadSkeletonJob);
49 d->m_backendSkeleton = nullptr;
50
51 Skeleton *skeleton = m_nodeManagers->skeletonManager()->data(handle: m_handle);
52 if (skeleton != nullptr) {
53 d->m_backendSkeleton = skeleton;
54 loadSkeleton(skeleton);
55 }
56}
57
58void LoadSkeletonJob::loadSkeleton(Skeleton *skeleton)
59{
60 qCDebug(Jobs) << Q_FUNC_INFO << skeleton->source();
61 skeleton->clearData();
62
63 // Load the data
64 switch (skeleton->dataType()) {
65 case Skeleton::File:
66 loadSkeletonFromUrl(skeleton);
67 break;
68
69 case Skeleton::Data:
70 loadSkeletonFromData(skeleton);
71 break;
72
73 default:
74 Q_UNREACHABLE();
75 }
76
77 // If using a loader inform the frontend of the status change.
78 // Don't bother if asked to create frontend joints though. When
79 // the backend gets notified of those joints we'll update the
80 // status at that point.
81 if (skeleton->dataType() == Skeleton::File && !skeleton->createJoints()) {
82 if (skeleton->jointCount() == 0)
83 skeleton->setStatus(Qt3DCore::QSkeletonLoader::Error);
84 else
85 skeleton->setStatus(Qt3DCore::QSkeletonLoader::Ready);
86 }
87
88 qCDebug(Jobs) << "Loaded skeleton data:" << *skeleton;
89}
90
91void LoadSkeletonJob::loadSkeletonFromUrl(Skeleton *skeleton)
92{
93 Q_D(LoadSkeletonJob);
94
95 using namespace Qt3DCore;
96
97 // TODO: Handle remote files
98 QString filePath = Qt3DCore::QUrlHelper::urlToLocalFileOrQrc(url: skeleton->source());
99 QFileInfo info(filePath);
100 if (!info.exists()) {
101 qWarning() << "Could not open skeleton file:" << filePath;
102 skeleton->setStatus(Qt3DCore::QSkeletonLoader::Error);
103 return;
104 }
105
106 QFile file(filePath);
107 if (!file.open(flags: QIODevice::ReadOnly)) {
108 qWarning() << "Could not open skeleton file:" << filePath;
109 skeleton->setStatus(QSkeletonLoader::Error);
110 return;
111 }
112
113 // TODO: Make plugin based for more file type support. For now gltf or native
114 const QString ext = info.suffix();
115 SkeletonData skeletonData;
116 if (ext == QLatin1String("gltf")) {
117 GLTFSkeletonLoader loader;
118 loader.load(ioDev: &file);
119 skeletonData = loader.createSkeleton(skeletonName: skeleton->name());
120
121 // If the user has requested it, create the frontend nodes for the joints
122 // and send them to the (soon to be owning) QSkeletonLoader.
123 if (skeleton->createJoints()) {
124 QJoint *rootJoint = createFrontendJoints(skeletonData);
125 if (!rootJoint) {
126 qWarning() << "Failed to create frontend joints";
127 skeleton->setStatus(QSkeletonLoader::Error);
128 return;
129 }
130
131 // Move the QJoint tree to the main thread and notify the
132 // corresponding QSkeletonLoader
133 const auto appThread = QCoreApplication::instance()->thread();
134 rootJoint->moveToThread(thread: appThread);
135
136 d->m_loadedRootJoint = rootJoint;
137
138 // Clear the skeleton data. It will be recreated from the
139 // frontend joints. A little bit inefficient but ensures
140 // that joints created this way and via QSkeleton go through
141 // the same code path.
142 skeletonData = SkeletonData();
143 }
144 } else if (ext == QLatin1String("json")) {
145 // TODO: Support native skeleton type
146 } else {
147 qWarning() << "Unknown skeleton file type:" << ext;
148 skeleton->setStatus(QSkeletonLoader::Error);
149 return;
150 }
151
152 skeleton->setSkeletonData(skeletonData);
153}
154
155void LoadSkeletonJob::loadSkeletonFromData(Skeleton *skeleton)
156{
157 // Recurse down through the joint hierarchy and process it into
158 // the vector of joints used within SkeletonData. The recursion
159 // ensures that a parent always appears before its children in
160 // the vector of JointInfo objects.
161 //
162 // In addition, we set up a mapping from the joint ids to the
163 // index of the corresponding JointInfo object in the vector.
164 // This will allow us to easily update entries in the vector of
165 // JointInfos when a Joint node marks itself as dirty.
166 const int rootParentIndex = -1;
167 auto skeletonData = skeleton->skeletonData();
168 processJointHierarchy(jointId: skeleton->rootJointId(), parentJointIndex: rootParentIndex, skeletonData);
169 skeleton->setSkeletonData(skeletonData);
170}
171
172Qt3DCore::QJoint *LoadSkeletonJob::createFrontendJoints(const SkeletonData &skeletonData) const
173{
174 if (skeletonData.joints.isEmpty())
175 return nullptr;
176
177 // Create frontend joints from the joint info objects
178 QList<Qt3DCore::QJoint *> frontendJoints;
179 const qsizetype jointCount = skeletonData.joints.size();
180 frontendJoints.reserve(asize: jointCount);
181 for (qsizetype i = 0; i < jointCount; ++i) {
182 const QMatrix4x4 &inverseBindMatrix = skeletonData.joints[i].inverseBindPose;
183 const QString &jointName = skeletonData.jointNames[i];
184 const Qt3DCore::Sqt &localPose = skeletonData.localPoses[i];
185 frontendJoints.push_back(t: createFrontendJoint(jointName, localPose, inverseBindMatrix));
186 }
187
188 // Now go through and resolve the parent for each joint
189 for (qsizetype i = 0; i < frontendJoints.size(); ++i) {
190 const auto parentIndex = skeletonData.joints[i].parentIndex;
191 if (parentIndex == -1)
192 continue;
193
194 // It's not enough to just set up the QObject parent-child relationship.
195 // We need to explicitly add the child to the parent's list of joints so
196 // that information is then propagated to the backend.
197 frontendJoints[parentIndex]->addChildJoint(joint: frontendJoints[i]);
198 }
199
200 return frontendJoints[0];
201}
202
203Qt3DCore::QJoint *LoadSkeletonJob::createFrontendJoint(const QString &jointName,
204 const Qt3DCore::Sqt &localPose,
205 const QMatrix4x4 &inverseBindMatrix) const
206{
207 auto joint = Qt3DCore::QAbstractNodeFactory::createNode<Qt3DCore::QJoint>(type: "QJoint");
208 joint->setTranslation(localPose.translation);
209 joint->setRotation(localPose.rotation);
210 joint->setScale(localPose.scale);
211 joint->setInverseBindMatrix(inverseBindMatrix);
212 joint->setName(jointName);
213 return joint;
214}
215
216void LoadSkeletonJob::processJointHierarchy(Qt3DCore::QNodeId jointId,
217 int parentJointIndex,
218 SkeletonData &skeletonData)
219{
220 // Lookup the joint, create a JointInfo, and add an entry to the index map
221 Joint *joint = m_nodeManagers->jointManager()->lookupResource(id: jointId);
222 Q_ASSERT(joint);
223 joint->setOwningSkeleton(m_handle);
224 const JointInfo jointInfo(joint, parentJointIndex);
225 skeletonData.joints.push_back(t: jointInfo);
226 skeletonData.localPoses.push_back(t: joint->localPose());
227 skeletonData.jointNames.push_back(t: joint->name());
228
229 const qsizetype jointIndex = skeletonData.joints.size() - 1;
230 const HJoint jointHandle = m_nodeManagers->jointManager()->lookupHandle(id: jointId);
231 skeletonData.jointIndices.insert(key: jointHandle, value: jointIndex);
232
233 // Recurse to the children
234 const auto childIds = joint->childJointIds();
235 for (const auto &childJointId : childIds)
236 processJointHierarchy(jointId: childJointId, parentJointIndex: jointIndex, skeletonData);
237}
238
239void LoadSkeletonJobPrivate::postFrame(Qt3DCore::QAspectManager *manager)
240{
241 if (!m_backendSkeleton)
242 return;
243
244 using namespace Qt3DCore;
245 QAbstractSkeleton *node = qobject_cast<QAbstractSkeleton *>(object: manager->lookupNode(id: m_backendSkeleton->peerId()));
246 if (!node)
247 return;
248
249 QAbstractSkeletonPrivate *dnode = QAbstractSkeletonPrivate::get(q: node);
250 dnode->m_jointCount = m_backendSkeleton->jointCount();
251 dnode->m_jointNames = m_backendSkeleton->jointNames();
252 dnode->m_localPoses = m_backendSkeleton->localPoses();
253 dnode->update();
254
255 QSkeletonLoader *loaderNode = qobject_cast<QSkeletonLoader *>(object: node);
256 if (loaderNode) {
257 QSkeletonLoaderPrivate *dloaderNode = static_cast<QSkeletonLoaderPrivate *>(QSkeletonLoaderPrivate::get(q: loaderNode));
258 dloaderNode->setStatus(m_backendSkeleton->status());
259
260 if (m_loadedRootJoint) {
261 dloaderNode->setRootJoint(m_loadedRootJoint);
262 m_loadedRootJoint = nullptr;
263 }
264 }
265}
266
267} // namespace Render
268} // namespace Qt3DRender
269
270QT_END_NAMESPACE
271

source code of qt3d/src/render/jobs/loadskeletonjob.cpp