mirror of https://github.com/qt/qt3d.git
Move skeleton loading code to job
Also removes last couple of messages updating backend to frontend. Change-Id: I65056c7cf5ff06efab9c9a205f843ed882f9c0be Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
This commit is contained in:
parent
06567dc188
commit
e504957c84
|
|
@ -36,24 +36,19 @@
|
|||
|
||||
#include "skeleton_p.h"
|
||||
|
||||
#include <Qt3DCore/qjoint.h>
|
||||
#include <Qt3DCore/qpropertyupdatedchange.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <Qt3DCore/qjoint.h>
|
||||
#include <Qt3DRender/private/abstractrenderer_p.h>
|
||||
#include <Qt3DRender/private/gltfskeletonloader_p.h>
|
||||
#include <Qt3DRender/private/managers_p.h>
|
||||
#include <Qt3DRender/private/nodemanagers_p.h>
|
||||
#include <Qt3DRender/private/renderlogging_p.h>
|
||||
#include <Qt3DRender/private/qurlhelper_p.h>
|
||||
#include <Qt3DCore/private/qskeletoncreatedchange_p.h>
|
||||
#include <Qt3DCore/private/qskeleton_p.h>
|
||||
#include <Qt3DCore/private/qskeletonloader_p.h>
|
||||
#include <Qt3DCore/private/qmath3d_p.h>
|
||||
#include <Qt3DCore/private/qabstractnodefactory_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
|
@ -146,192 +141,8 @@ void Skeleton::syncFromFrontEnd(const QNode *frontEnd, bool firstTime)
|
|||
|
||||
void Skeleton::setStatus(QSkeletonLoader::Status status)
|
||||
{
|
||||
if (status != m_status) {
|
||||
if (status != m_status)
|
||||
m_status = status;
|
||||
Qt3DCore::QPropertyUpdatedChangePtr e = Qt3DCore::QPropertyUpdatedChangePtr::create(peerId());
|
||||
e->setDeliveryFlags(Qt3DCore::QSceneChange::DeliverToAll);
|
||||
e->setPropertyName("status");
|
||||
e->setValue(QVariant::fromValue(m_status));
|
||||
notifyObservers(e);
|
||||
}
|
||||
}
|
||||
|
||||
void Skeleton::loadSkeleton()
|
||||
{
|
||||
qCDebug(Jobs) << Q_FUNC_INFO << m_source;
|
||||
clearData();
|
||||
|
||||
// Load the data
|
||||
switch (m_dataType) {
|
||||
case File:
|
||||
loadSkeletonFromUrl();
|
||||
break;
|
||||
|
||||
case Data:
|
||||
loadSkeletonFromData();
|
||||
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 (m_dataType == File && !m_createJoints) {
|
||||
if (jointCount() == 0)
|
||||
setStatus(QSkeletonLoader::Error);
|
||||
else
|
||||
setStatus(QSkeletonLoader::Ready);
|
||||
}
|
||||
|
||||
qCDebug(Jobs) << "Loaded skeleton data:" << *this;
|
||||
}
|
||||
|
||||
void Skeleton::loadSkeletonFromUrl()
|
||||
{
|
||||
// TODO: Handle remote files
|
||||
QString filePath = Qt3DRender::QUrlHelper::urlToLocalFileOrQrc(m_source);
|
||||
QFileInfo info(filePath);
|
||||
if (!info.exists()) {
|
||||
qWarning() << "Could not open skeleton file:" << filePath;
|
||||
setStatus(Qt3DCore::QSkeletonLoader::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "Could not open skeleton file:" << filePath;
|
||||
setStatus(Qt3DCore::QSkeletonLoader::Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Make plugin based for more file type support. For now gltf or native
|
||||
const QString ext = info.suffix();
|
||||
if (ext == QLatin1String("gltf")) {
|
||||
GLTFSkeletonLoader loader;
|
||||
loader.load(&file);
|
||||
m_skeletonData = loader.createSkeleton(m_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 (m_createJoints) {
|
||||
std::unique_ptr<QJoint> rootJoint(createFrontendJoints(m_skeletonData));
|
||||
if (!rootJoint) {
|
||||
qWarning() << "Failed to create frontend joints";
|
||||
setStatus(Qt3DCore::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);
|
||||
|
||||
auto e = QJointChangePtr::create(peerId());
|
||||
e->setDeliveryFlags(Qt3DCore::QSceneChange::Nodes);
|
||||
e->setPropertyName("rootJoint");
|
||||
e->data = std::move(rootJoint);
|
||||
notifyObservers(e);
|
||||
|
||||
// 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.
|
||||
m_skeletonData = SkeletonData();
|
||||
}
|
||||
} else if (ext == QLatin1String("json")) {
|
||||
// TODO: Support native skeleton type
|
||||
} else {
|
||||
qWarning() << "Unknown skeleton file type:" << ext;
|
||||
setStatus(Qt3DCore::QSkeletonLoader::Error);
|
||||
return;
|
||||
}
|
||||
m_skinningPalette.resize(m_skeletonData.joints.size());
|
||||
}
|
||||
|
||||
void Skeleton::loadSkeletonFromData()
|
||||
{
|
||||
// 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;
|
||||
processJointHierarchy(m_rootJointId, rootParentIndex, m_skeletonData);
|
||||
m_skinningPalette.resize(m_skeletonData.joints.size());
|
||||
}
|
||||
|
||||
Qt3DCore::QJoint *Skeleton::createFrontendJoints(const SkeletonData &skeletonData) const
|
||||
{
|
||||
if (skeletonData.joints.isEmpty())
|
||||
return nullptr;
|
||||
|
||||
// Create frontend joints from the joint info objects
|
||||
QVector<QJoint *> 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 *Skeleton::createFrontendJoint(const QString &jointName,
|
||||
const Qt3DCore::Sqt &localPose,
|
||||
const QMatrix4x4 &inverseBindMatrix) const
|
||||
{
|
||||
auto joint = QAbstractNodeFactory::createNode<QJoint>("QJoint");
|
||||
joint->setTranslation(localPose.translation);
|
||||
joint->setRotation(localPose.rotation);
|
||||
joint->setScale(localPose.scale);
|
||||
joint->setInverseBindMatrix(inverseBindMatrix);
|
||||
joint->setName(jointName);
|
||||
return joint;
|
||||
}
|
||||
|
||||
void Skeleton::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_renderer->nodeManagers()->jointManager()->lookupResource(jointId);
|
||||
Q_ASSERT(joint);
|
||||
joint->setOwningSkeleton(m_skeletonHandle);
|
||||
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_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 Skeleton::clearData()
|
||||
|
|
@ -343,6 +154,12 @@ void Skeleton::clearData()
|
|||
m_skeletonData.jointIndices.clear();
|
||||
}
|
||||
|
||||
void Skeleton::setSkeletonData(const SkeletonData &data)
|
||||
{
|
||||
m_skeletonData = data;
|
||||
m_skinningPalette.resize(m_skeletonData.joints.size());
|
||||
}
|
||||
|
||||
// Called from UpdateSkinningPaletteJob
|
||||
void Skeleton::setLocalPose(HJoint jointHandle, const Qt3DCore::Sqt &localPose)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -75,6 +75,12 @@ class SkeletonManager;
|
|||
class Q_AUTOTEST_EXPORT Skeleton : public BackendNode
|
||||
{
|
||||
public:
|
||||
enum SkeletonDataType {
|
||||
Unknown,
|
||||
File,
|
||||
Data
|
||||
};
|
||||
|
||||
Skeleton();
|
||||
|
||||
void setSkeletonManager(SkeletonManager *skeletonManager) { m_skeletonManager = skeletonManager; }
|
||||
|
|
@ -89,6 +95,8 @@ public:
|
|||
Qt3DCore::QSkeletonLoader::Status status() const { return m_status; }
|
||||
|
||||
QUrl source() const { return m_source; }
|
||||
SkeletonDataType dataType() const { return m_dataType; }
|
||||
bool createJoints() const { return m_createJoints; }
|
||||
|
||||
void setName(const QString &name) { m_name = name; }
|
||||
QString name() const { return m_name; }
|
||||
|
|
@ -101,34 +109,21 @@ public:
|
|||
Qt3DCore::QNodeId rootJointId() const { return m_rootJointId; }
|
||||
|
||||
// Called from jobs
|
||||
void loadSkeleton();
|
||||
void setLocalPose(HJoint jointHandle, const Qt3DCore::Sqt &localPose);
|
||||
QVector<QMatrix4x4> calculateSkinningMatrixPalette();
|
||||
|
||||
void clearData();
|
||||
void setSkeletonData(const SkeletonData &data);
|
||||
const SkeletonData &skeletonData() const { return m_skeletonData; }
|
||||
SkeletonData skeletonData() { return m_skeletonData; }
|
||||
|
||||
// Allow unit tests to set the data type
|
||||
#if !defined(QT_BUILD_INTERNAL)
|
||||
private:
|
||||
#endif
|
||||
enum SkeletonDataType {
|
||||
Unknown,
|
||||
File,
|
||||
Data
|
||||
};
|
||||
#if defined(QT_BUILD_INTERNAL)
|
||||
public:
|
||||
void setDataType(SkeletonDataType dataType) { m_dataType = dataType; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
void loadSkeletonFromUrl();
|
||||
void loadSkeletonFromData();
|
||||
Qt3DCore::QJoint *createFrontendJoints(const SkeletonData &skeletonData) const;
|
||||
Qt3DCore::QJoint *createFrontendJoint(const QString &jointName,
|
||||
const Qt3DCore::Sqt &localPose,
|
||||
const QMatrix4x4 &inverseBindMatrix) const;
|
||||
void processJointHierarchy(Qt3DCore::QNodeId jointId, int parentJointIndex, SkeletonData &skeletonData);
|
||||
void clearData();
|
||||
|
||||
QVector<QMatrix4x4> m_skinningPalette;
|
||||
|
||||
// QSkeletonLoader Properties
|
||||
|
|
|
|||
|
|
@ -35,12 +35,18 @@
|
|||
****************************************************************************/
|
||||
|
||||
#include "loadskeletonjob_p.h"
|
||||
#include <Qt3DCore/qjoint.h>
|
||||
#include <Qt3DCore/qabstractskeleton.h>
|
||||
#include <Qt3DCore/private/qaspectmanager_p.h>
|
||||
#include <Qt3DCore/qskeletonloader.h>
|
||||
#include <Qt3DCore/private/qabstractskeleton_p.h>
|
||||
#include <Qt3DRender/private/nodemanagers_p.h>
|
||||
#include <Qt3DCore/private/qabstractnodefactory_p.h>
|
||||
#include <Qt3DCore/private/qaspectmanager_p.h>
|
||||
#include <Qt3DCore/private/qskeletonloader_p.h>
|
||||
#include <Qt3DRender/private/managers_p.h>
|
||||
#include <Qt3DRender/private/nodemanagers_p.h>
|
||||
#include <Qt3DRender/private/job_common_p.h>
|
||||
#include <Qt3DRender/private/qurlhelper_p.h>
|
||||
#include <Qt3DRender/private/gltfskeletonloader_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
|
|
@ -50,12 +56,13 @@ namespace Render {
|
|||
class LoadSkeletonJobPrivate : public Qt3DCore::QAspectJobPrivate
|
||||
{
|
||||
public:
|
||||
LoadSkeletonJobPrivate() : m_backendSkeleton(nullptr) { }
|
||||
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)
|
||||
|
|
@ -66,10 +73,6 @@ LoadSkeletonJob::LoadSkeletonJob(const HSkeleton &handle)
|
|||
SET_JOB_RUN_STAT_TYPE(this, JobTypes::LoadSkeleton, 0)
|
||||
}
|
||||
|
||||
LoadSkeletonJob::~LoadSkeletonJob()
|
||||
{
|
||||
}
|
||||
|
||||
void LoadSkeletonJob::run()
|
||||
{
|
||||
Q_D(LoadSkeletonJob);
|
||||
|
|
@ -78,10 +81,191 @@ void LoadSkeletonJob::run()
|
|||
Skeleton *skeleton = m_nodeManagers->skeletonManager()->data(m_handle);
|
||||
if (skeleton != nullptr) {
|
||||
d->m_backendSkeleton = skeleton;
|
||||
skeleton->loadSkeleton();
|
||||
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_D(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<Qt3DCore::QJoint *> 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<Qt3DCore::QJoint>("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)
|
||||
|
|
@ -97,6 +281,17 @@ void LoadSkeletonJobPrivate::postFrame(Qt3DCore::QAspectManager *manager)
|
|||
dnode->m_jointNames = m_backendSkeleton->jointNames();
|
||||
dnode->m_localPoses = m_backendSkeleton->localPoses();
|
||||
dnode->update();
|
||||
|
||||
QSkeletonLoader *loaderNode = qobject_cast<QSkeletonLoader *>(node);
|
||||
if (loaderNode) {
|
||||
QSkeletonLoaderPrivate *dloaderNode = static_cast<QSkeletonLoaderPrivate *>(QSkeletonLoaderPrivate::get(loaderNode));
|
||||
dloaderNode->setStatus(m_backendSkeleton->status());
|
||||
|
||||
if (m_loadedRootJoint) {
|
||||
dloaderNode->m_rootJoint = m_loadedRootJoint;
|
||||
m_loadedRootJoint = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Render
|
||||
|
|
|
|||
|
|
@ -51,11 +51,15 @@
|
|||
#include <Qt3DCore/qaspectjob.h>
|
||||
|
||||
#include <QtCore/qsharedpointer.h>
|
||||
|
||||
#include <Qt3DRender/private/skeletondata_p.h>
|
||||
#include <Qt3DRender/private/handle_types_p.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
namespace Qt3DCore {
|
||||
class QJoint;
|
||||
}
|
||||
|
||||
namespace Qt3DRender {
|
||||
namespace Render {
|
||||
|
||||
|
|
@ -66,12 +70,22 @@ class LoadSkeletonJob : public Qt3DCore::QAspectJob
|
|||
{
|
||||
public:
|
||||
explicit LoadSkeletonJob(const HSkeleton &handle);
|
||||
~LoadSkeletonJob();
|
||||
|
||||
void setNodeManagers(NodeManagers *nodeManagers) { m_nodeManagers = nodeManagers; }
|
||||
|
||||
protected:
|
||||
void run() override;
|
||||
void loadSkeleton(Skeleton *skeleton);
|
||||
void loadSkeletonFromUrl(Skeleton *skeleton);
|
||||
void loadSkeletonFromData(Skeleton *skeleton);
|
||||
Qt3DCore::QJoint *createFrontendJoints(const SkeletonData &skeletonData) const;
|
||||
Qt3DCore::QJoint *createFrontendJoint(const QString &jointName,
|
||||
const Qt3DCore::Sqt &localPose,
|
||||
const QMatrix4x4 &inverseBindMatrix) const;
|
||||
void processJointHierarchy(Qt3DCore::QNodeId jointId,
|
||||
int parentJointIndex,
|
||||
SkeletonData &skeletonData);
|
||||
|
||||
HSkeleton m_handle;
|
||||
NodeManagers *m_nodeManagers;
|
||||
|
||||
|
|
|
|||
|
|
@ -220,30 +220,6 @@ private Q_SLOTS:
|
|||
QTest::newRow("inverseBind") << m << localPose << name << joint;
|
||||
}
|
||||
|
||||
void checkCreateFrontendJoint()
|
||||
{
|
||||
// GIVEN
|
||||
Skeleton backendSkeleton;
|
||||
QFETCH(QMatrix4x4, inverseBindMatrix);
|
||||
QFETCH(Qt3DCore::Sqt, localPose);
|
||||
QFETCH(QString, jointName);
|
||||
QFETCH(QJoint *, expectedJoint);
|
||||
|
||||
// WHEN
|
||||
const QJoint *actualJoint = backendSkeleton.createFrontendJoint(jointName, localPose, inverseBindMatrix);
|
||||
|
||||
// THEN
|
||||
QCOMPARE(actualJoint->scale(), expectedJoint->scale());
|
||||
QCOMPARE(actualJoint->rotation(), expectedJoint->rotation());
|
||||
QCOMPARE(actualJoint->translation(), expectedJoint->translation());
|
||||
QCOMPARE(actualJoint->inverseBindMatrix(), expectedJoint->inverseBindMatrix());
|
||||
QCOMPARE(actualJoint->name(), expectedJoint->name());
|
||||
|
||||
// Cleanup
|
||||
delete actualJoint;
|
||||
delete expectedJoint;
|
||||
}
|
||||
|
||||
void checkCreateFrontendJoints_data()
|
||||
{
|
||||
QTest::addColumn<SkeletonData>("skeletonData");
|
||||
|
|
@ -307,46 +283,6 @@ private Q_SLOTS:
|
|||
|
||||
QTest::newRow("deep") << skeletonData << rootJoint;
|
||||
}
|
||||
|
||||
void checkCreateFrontendJoints()
|
||||
{
|
||||
// GIVEN
|
||||
Skeleton backendSkeleton;
|
||||
QFETCH(SkeletonData, skeletonData);
|
||||
QFETCH(QJoint *, expectedRootJoint);
|
||||
|
||||
// WHEN
|
||||
QJoint *actualRootJoint = backendSkeleton.createFrontendJoints(skeletonData);
|
||||
|
||||
// THEN
|
||||
if (skeletonData.joints.isEmpty()) {
|
||||
QVERIFY(actualRootJoint == expectedRootJoint); // nullptr
|
||||
return;
|
||||
}
|
||||
|
||||
// Linearise the tree of joints and check them against the skeletonData
|
||||
QVector<QJoint *> joints = linearizeTree(actualRootJoint);
|
||||
QCOMPARE(joints.size(), skeletonData.joints.size());
|
||||
for (int i = 0; i < joints.size(); ++i) {
|
||||
// Check the translations match
|
||||
QCOMPARE(joints[i]->translation(), skeletonData.localPoses[i].translation);
|
||||
}
|
||||
|
||||
// Now we know the order of Joints match. Check the parents match too
|
||||
for (int i = 0; i < joints.size(); ++i) {
|
||||
// Get parent index from joint info
|
||||
const int parentIndex = skeletonData.joints[i].parentIndex;
|
||||
if (parentIndex == -1) {
|
||||
QVERIFY(joints[i]->parent() == nullptr);
|
||||
} else {
|
||||
QCOMPARE(joints[i]->parent(), joints[parentIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
delete actualRootJoint;
|
||||
delete expectedRootJoint;
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_Skeleton)
|
||||
|
|
|
|||
Loading…
Reference in New Issue