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

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