diff options
author | Sean Harmer <sean.harmer@kdab.com> | 2017-08-06 18:58:46 +0100 |
---|---|---|
committer | Sean Harmer <sean.harmer@kdab.com> | 2017-08-16 13:19:23 +0000 |
commit | 5c6634f2d1cd9a016142c3641ab6d797f5fe4ba7 (patch) | |
tree | ccbad78b2839b26b1f10cf5aa112786df56bd018 /tests | |
parent | b236f982170779a1836f7d391428287921422163 (diff) |
Update skinned mesh example to expose joints of object
To do this we add a helper Sqt struct that wraps up an affine
transformation as a scale vector, rotation quaternion and a
translation vector. This is the format in which the animation
aspect will animate the joints later so it's easier to keep
the transforms split like this. It's also less data to move
around compared with a 4x4 matrix (10 vs 16 floats, 12 including
the padding).
Change-Id: Iaa30b5ef5d1635cc208ead918827140cf2765908
Reviewed-by: Paul Lemire <paul.lemire@kdab.com>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/auto/core/qskeletonloader/tst_qskeletonloader.cpp | 77 | ||||
-rw-r--r-- | tests/auto/render/skeleton/tst_skeleton.cpp | 170 | ||||
-rw-r--r-- | tests/manual/skinned-mesh/SkinnedEntity.qml | 2 | ||||
-rw-r--r-- | tests/manual/skinned-mesh/main.qml | 1 |
4 files changed, 249 insertions, 1 deletions
diff --git a/tests/auto/core/qskeletonloader/tst_qskeletonloader.cpp b/tests/auto/core/qskeletonloader/tst_qskeletonloader.cpp index db643fc43..1386429d4 100644 --- a/tests/auto/core/qskeletonloader/tst_qskeletonloader.cpp +++ b/tests/auto/core/qskeletonloader/tst_qskeletonloader.cpp @@ -29,6 +29,7 @@ #include <QtTest/QTest> #include <Qt3DCore/qskeletonloader.h> +#include <Qt3DCore/qjoint.h> #include <Qt3DCore/private/qskeletonloader_p.h> #include <Qt3DCore/qpropertyupdatedchange.h> #include <Qt3DCore/qnodecreatedchange.h> @@ -52,6 +53,7 @@ private Q_SLOTS: // THEN QCOMPARE(skeleton.source(), QUrl()); QCOMPARE(skeleton.status(), QSkeletonLoader::NotReady); + QCOMPARE(skeleton.isCreateJointsEnabled(), false); } void checkPropertyChanges() @@ -78,6 +80,26 @@ private Q_SLOTS: QCOMPARE(skeleton.source(), newValue); QCOMPARE(spy.count(), 0); } + + { + // WHEN + QSignalSpy spy(&skeleton, SIGNAL(createJointsEnabledChanged(bool))); + const bool newValue(true); + skeleton.setCreateJointsEnabled(newValue); + + // THEN + QVERIFY(spy.isValid()); + QCOMPARE(skeleton.isCreateJointsEnabled(), newValue); + QCOMPARE(spy.count(), 1); + + // WHEN + spy.clear(); + skeleton.setCreateJointsEnabled(newValue); + + // THEN + QCOMPARE(skeleton.isCreateJointsEnabled(), newValue); + QCOMPARE(spy.count(), 0); + } } void checkCreationData() @@ -107,6 +129,7 @@ private Q_SLOTS: QCOMPARE(skeleton.isEnabled(), creationChangeData->isNodeEnabled()); QCOMPARE(skeleton.metaObject(), creationChangeData->metaObject()); QCOMPARE(skeleton.source(), data.source); + QCOMPARE(skeleton.isCreateJointsEnabled(), data.createJoints); } // WHEN @@ -122,15 +145,18 @@ private Q_SLOTS: QCOMPARE(creationChanges.size(), 1); const auto creationChangeData = qSharedPointerCast<QNodeCreatedChange<QSkeletonLoaderData>>(creationChanges.first()); + const QSkeletonLoaderData data = creationChangeData->data; QCOMPARE(skeleton.id(), creationChangeData->subjectId()); QCOMPARE(skeleton.isEnabled(), false); QCOMPARE(skeleton.isEnabled(), creationChangeData->isNodeEnabled()); QCOMPARE(skeleton.metaObject(), creationChangeData->metaObject()); + QCOMPARE(skeleton.source(), data.source); + QCOMPARE(skeleton.isCreateJointsEnabled(), data.createJoints); } } - void checkSourceUpdate() + void checkPropertyUpdates() { // GIVEN TestArbiter arbiter; @@ -160,6 +186,29 @@ private Q_SLOTS: QCOMPARE(arbiter.events.size(), 0); } + + { + // WHEN + skeleton.setCreateJointsEnabled(true); + QCoreApplication::processEvents(); + + // THEN + QCOMPARE(arbiter.events.size(), 1); + auto change = arbiter.events.first().staticCast<QPropertyUpdatedChange>(); + QCOMPARE(change->propertyName(), "createJointsEnabled"); + QCOMPARE(change->type(), PropertyUpdated); + + arbiter.events.clear(); + } + + { + // WHEN + skeleton.setCreateJointsEnabled(true); + QCoreApplication::processEvents(); + + // THEN + QCOMPARE(arbiter.events.size(), 0); + } } void checkStatusPropertyUpdate() @@ -194,6 +243,32 @@ private Q_SLOTS: QCOMPARE(arbiter.events.size(), 0); QCOMPARE(status(), newStatus); } + + void checkRootJointPropertyUpdate() + { + // GIVEN + qRegisterMetaType<Qt3DCore::QJoint*>(); + TestArbiter arbiter; + arbiter.setArbiterOnNode(this); + QSignalSpy spy(this, SIGNAL(rootJointChanged(Qt3DCore::QJoint*))); + std::unique_ptr<QJoint> root(new QJoint()); + + // THEN + QVERIFY(spy.isValid()); + QVERIFY(rootJoint() == nullptr); + + // WHEN + auto valueChange = QJointChangePtr::create(id()); + valueChange->setDeliveryFlags(Qt3DCore::QSceneChange::Nodes); + valueChange->setPropertyName("rootJoint"); + valueChange->data = std::move(root); + sceneChangeEvent(valueChange); + + // THEN + QCOMPARE(spy.count(), 1); + QCOMPARE(arbiter.events.size(), 1); + QVERIFY(rootJoint() != nullptr); + } }; QTEST_MAIN(tst_QSkeletonLoader) diff --git a/tests/auto/render/skeleton/tst_skeleton.cpp b/tests/auto/render/skeleton/tst_skeleton.cpp index 072b90c2f..64359fc78 100644 --- a/tests/auto/render/skeleton/tst_skeleton.cpp +++ b/tests/auto/render/skeleton/tst_skeleton.cpp @@ -29,6 +29,7 @@ #include <QtTest/QTest> #include <Qt3DRender/private/skeleton_p.h> #include <Qt3DRender/private/nodemanagers_p.h> +#include <Qt3DCore/qjoint.h> #include <Qt3DCore/qskeletonloader.h> #include <Qt3DCore/private/qnode_p.h> #include <Qt3DCore/private/qscene_p.h> @@ -43,6 +44,27 @@ using namespace Qt3DCore; using namespace Qt3DRender; using namespace Qt3DRender::Render; +Q_DECLARE_METATYPE(Qt3DRender::Render::JointInfo) +Q_DECLARE_METATYPE(Qt3DRender::Render::SkeletonData) + +namespace { + +void linearizeTreeHelper(QJoint *joint, QVector<QJoint *> &joints) +{ + joints.push_back(joint); + for (const auto child : joint->childJoints()) + linearizeTreeHelper(child, joints); +} + +QVector<QJoint *> linearizeTree(QJoint *rootJoint) +{ + QVector<QJoint *> joints; + linearizeTreeHelper(rootJoint, joints); + return joints; +} + +} + class tst_Skeleton : public Qt3DCore::QBackendNodeTester { Q_OBJECT @@ -169,6 +191,154 @@ private Q_SLOTS: arbiter.events.clear(); } + + void checkCreateFrontendJoint_data() + { + QTest::addColumn<JointInfo>("jointInfo"); + QTest::addColumn<QJoint *>("expectedJoint"); + + QTest::newRow("default") << JointInfo() << new QJoint(); + + const QVector3D t(1.0f, 2.0f, 3.0f); + const QQuaternion r = QQuaternion::fromAxisAndAngle(1.0f, 0.0f, 0.0f, 45.0f); + const QVector3D s(1.5f, 2.5f, 3.5f); + JointInfo jointInfo; + jointInfo.localPose.scale = s; + jointInfo.localPose.rotation = r; + jointInfo.localPose.translation = t; + + QJoint *joint = new QJoint(); + joint->setTranslation(t); + joint->setRotation(r); + joint->setScale(s); + QTest::newRow("localPose") << jointInfo << joint; + + QMatrix4x4 m; + m.rotate(r); + m.scale(QVector3D(1.0f, 1.0f, 1.0f) / s); + m.translate(-t); + jointInfo.inverseBindPose = m; + + joint = new QJoint(); + joint->setTranslation(t); + joint->setRotation(r); + joint->setScale(s); + joint->setInverseBindMatrix(m); + QTest::newRow("inverseBind") << jointInfo << joint; + } + + void checkCreateFrontendJoint() + { + // GIVEN + Skeleton backendSkeleton; + QFETCH(JointInfo, jointInfo); + QFETCH(QJoint *, expectedJoint); + + // WHEN + const QJoint *actualJoint = backendSkeleton.createFrontendJoint(jointInfo); + + // THEN + QCOMPARE(actualJoint->scale(), expectedJoint->scale()); + QCOMPARE(actualJoint->rotation(), expectedJoint->rotation()); + QCOMPARE(actualJoint->translation(), expectedJoint->translation()); + QCOMPARE(actualJoint->inverseBindMatrix(), expectedJoint->inverseBindMatrix()); + + // Cleanup + delete actualJoint; + delete expectedJoint; + } + + void checkCreateFrontendJoints_data() + { + QTest::addColumn<SkeletonData>("skeletonData"); + QTest::addColumn<QJoint *>("expectedRootJoint"); + + QTest::newRow("empty") << SkeletonData() << (QJoint*)nullptr; + + SkeletonData skeletonData; + JointInfo rootJointInfo; + skeletonData.joints.push_back(rootJointInfo); + const int childCount = 10; + for (int i = 0; i < childCount; ++i) { + JointInfo childJointInfo; + const float x = static_cast<float>(i); + childJointInfo.localPose.translation = QVector3D(x, x, x); + childJointInfo.parentIndex = 0; + skeletonData.joints.push_back(childJointInfo); + } + + QJoint *rootJoint = new QJoint(); + for (int i = 0; i < childCount; ++i) { + QJoint *childJoint = new QJoint(); + const float x = static_cast<float>(i); + childJoint->setTranslation(QVector3D(x, x, x)); + rootJoint->addChildJoint(childJoint); + } + + QTest::newRow("wide") << skeletonData << rootJoint; + + skeletonData.joints.clear(); + skeletonData.joints.push_back(rootJointInfo); + for (int i = 0; i < childCount; ++i) { + JointInfo childJointInfo; + const float x = static_cast<float>(i); + childJointInfo.localPose.translation = QVector3D(x, x, x); + childJointInfo.parentIndex = i; + skeletonData.joints.push_back(childJointInfo); + } + + rootJoint = new QJoint(); + QJoint *previousJoint = rootJoint; + for (int i = 0; i < childCount; ++i) { + QJoint *childJoint = new QJoint(); + const float x = static_cast<float>(i); + childJoint->setTranslation(QVector3D(x, x, x)); + previousJoint->addChildJoint(childJoint); + previousJoint = childJoint; + } + + 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.joints[i].localPose.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) diff --git a/tests/manual/skinned-mesh/SkinnedEntity.qml b/tests/manual/skinned-mesh/SkinnedEntity.qml index 455f3869e..3abafa209 100644 --- a/tests/manual/skinned-mesh/SkinnedEntity.qml +++ b/tests/manual/skinned-mesh/SkinnedEntity.qml @@ -8,6 +8,7 @@ Entity { property Effect effect: skinnedPbrEffect property url source: "" + property alias createJointsEnabled: skeleton.createJointsEnabled property alias transform: transform property color baseColor: "red" @@ -21,6 +22,7 @@ Entity { }, Armature { skeleton: SkeletonLoader { + id: skeleton source: root.source onStatusChanged: console.log("skeleton loader status: " + status) onJointCountChanged: console.log("skeleton has " + jointCount + " joints") diff --git a/tests/manual/skinned-mesh/main.qml b/tests/manual/skinned-mesh/main.qml index 31c618382..a3d8e3d12 100644 --- a/tests/manual/skinned-mesh/main.qml +++ b/tests/manual/skinned-mesh/main.qml @@ -73,5 +73,6 @@ DefaultSceneEntity { baseColor: "blue" transform.scale: 0.05 transform.translation: Qt.vector3d(0.5, 0.25, 0.0) + createJointsEnabled: true } } |