blob: 76909d1241851958ab234ef29bdb4e04bdc69694 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt3D module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** 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 https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QTest>
#include <Qt3DAnimation/private/animationclip_p.h>
#include <Qt3DAnimation/private/animationutils_p.h>
#include <Qt3DAnimation/private/blendedclipanimator_p.h>
#include <Qt3DAnimation/private/clock_p.h>
#include <Qt3DAnimation/private/channelmapper_p.h>
#include <Qt3DAnimation/private/channelmapping_p.h>
#include <Qt3DAnimation/private/clipblendvalue_p.h>
#include <Qt3DAnimation/private/handler_p.h>
#include <Qt3DAnimation/private/additiveclipblend_p.h>
#include <Qt3DAnimation/private/lerpclipblend_p.h>
#include <Qt3DAnimation/private/managers_p.h>
#include <QtGui/qvector2d.h>
#include <QtGui/qvector3d.h>
#include <QtGui/qvector4d.h>
#include <QtGui/qquaternion.h>
#include <QtGui/qcolor.h>
#include <QtCore/qbitarray.h>
#include <qbackendnodetester.h>
#include <testpostmanarbiter.h>
using namespace Qt3DAnimation::Animation;
Q_DECLARE_METATYPE(Qt3DAnimation::Animation::Handler*)
Q_DECLARE_METATYPE(QVector<ChannelMapping *>)
Q_DECLARE_METATYPE(Clock *)
Q_DECLARE_METATYPE(ChannelMapper *)
Q_DECLARE_METATYPE(AnimationClip *)
Q_DECLARE_METATYPE(QVector<MappingData>)
Q_DECLARE_METATYPE(Channel)
Q_DECLARE_METATYPE(AnimatorEvaluationData)
Q_DECLARE_METATYPE(ClipEvaluationData)
Q_DECLARE_METATYPE(ClipAnimator *)
Q_DECLARE_METATYPE(BlendedClipAnimator *)
Q_DECLARE_METATYPE(QVector<ChannelNameAndType>)
Q_DECLARE_METATYPE(QVector<AnimationCallbackAndValue>)
Q_DECLARE_METATYPE(ClipFormat)
Q_DECLARE_METATYPE(ChannelNameAndType)
namespace {
class MeanBlendNode : public ClipBlendNode
{
public:
MeanBlendNode()
: ClipBlendNode(ClipBlendNode::LerpBlendType)
{}
void setValueNodeIds(Qt3DCore::QNodeId value1Id,
Qt3DCore::QNodeId value2Id)
{
m_value1Id = value1Id;
m_value2Id = value2Id;
}
inline QVector<Qt3DCore::QNodeId> allDependencyIds() const override
{
return currentDependencyIds();
}
QVector<Qt3DCore::QNodeId> currentDependencyIds() const final
{
return QVector<Qt3DCore::QNodeId>() << m_value1Id << m_value2Id;
}
using ClipBlendNode::setClipResults;
double duration() const final { return 0.0f; }
protected:
ClipResults doBlend(const QVector<ClipResults> &blendData) const final
{
Q_ASSERT(blendData.size() == 2);
const int elementCount = blendData.first().size();
ClipResults blendResults(elementCount);
for (int i = 0; i < elementCount; ++i)
blendResults[i] = 0.5f * (blendData[0][i] + blendData[1][i]);
return blendResults;
}
private:
Qt3DCore::QNodeId m_value1Id;
Qt3DCore::QNodeId m_value2Id;
};
bool fuzzyCompare(float x1, float x2)
{
if (qFuzzyIsNull(x1) && qFuzzyIsNull(x2)) {
return true;
} else if ((qFuzzyIsNull(x1) && !qFuzzyIsNull(x2)) ||
(!qFuzzyIsNull(x1) && qFuzzyIsNull(x2))) {
return false;
} else {
return qFuzzyCompare(x1, x2);
}
}
class DummyCallback : public Qt3DAnimation::QAnimationCallback
{
public:
void valueChanged(const QVariant &) override { }
};
} // anonymous
class tst_AnimationUtils : public Qt3DCore::QBackendNodeTester
{
Q_OBJECT
public:
ChannelMapping *createChannelMapping(Handler *handler,
const QString &channelName,
const Qt3DCore::QNodeId targetId,
const char *propertyName,
int type,
int componentCount)
{
auto channelMappingId = Qt3DCore::QNodeId::createId();
ChannelMapping *channelMapping = handler->channelMappingManager()->getOrCreateResource(channelMappingId);
setPeerId(channelMapping, channelMappingId);
channelMapping->setHandler(handler);
channelMapping->setTargetId(targetId);
channelMapping->setPropertyName(propertyName);
channelMapping->setChannelName(channelName);
channelMapping->setType(type);
channelMapping->setMappingType(ChannelMapping::ChannelMappingType);
channelMapping->setComponentCount(componentCount);
return channelMapping;
}
ChannelMapping *createChannelMapping(Handler *handler,
const Qt3DCore::QNodeId skeletonId)
{
auto channelMappingId = Qt3DCore::QNodeId::createId();
ChannelMapping *channelMapping = handler->channelMappingManager()->getOrCreateResource(channelMappingId);
setPeerId(channelMapping, channelMappingId);
channelMapping->setHandler(handler);
channelMapping->setSkeletonId(skeletonId);
channelMapping->setMappingType(ChannelMapping::SkeletonMappingType);
return channelMapping;
}
ChannelMapper *createChannelMapper(Handler *handler,
const QVector<Qt3DCore::QNodeId> &mappingIds)
{
auto channelMapperId = Qt3DCore::QNodeId::createId();
ChannelMapper *channelMapper = handler->channelMapperManager()->getOrCreateResource(channelMapperId);
setPeerId(channelMapper, channelMapperId);
channelMapper->setMappingIds(mappingIds);
return channelMapper;
}
AnimationClip *createAnimationClipLoader(Handler *handler,
const QUrl &source)
{
auto clipId = Qt3DCore::QNodeId::createId();
AnimationClip *clip = handler->animationClipLoaderManager()->getOrCreateResource(clipId);
setPeerId(clip, clipId);
clip->setDataType(AnimationClip::File);
clip->setSource(source);
clip->loadAnimation();
return clip;
}
ClipAnimator *createClipAnimator(Handler *handler,
qint64 globalStartTimeNS,
int loops)
{
auto animatorId = Qt3DCore::QNodeId::createId();
ClipAnimator *animator = handler->clipAnimatorManager()->getOrCreateResource(animatorId);
setPeerId(animator, animatorId);
animator->setStartTime(globalStartTimeNS);
animator->setLoops(loops);
return animator;
}
BlendedClipAnimator *createBlendedClipAnimator(Handler *handler,
qint64 globalStartTimeNS,
int loops)
{
auto animatorId = Qt3DCore::QNodeId::createId();
BlendedClipAnimator *animator = handler->blendedClipAnimatorManager()->getOrCreateResource(animatorId);
setPeerId(animator, animatorId);
animator->setStartTime(globalStartTimeNS);
animator->setLoops(loops);
return animator;
}
LerpClipBlend *createLerpClipBlend(Handler *handler)
{
auto lerpId = Qt3DCore::QNodeId::createId();
LerpClipBlend *lerp = new LerpClipBlend();
setPeerId(lerp, lerpId);
lerp->setClipBlendNodeManager(handler->clipBlendNodeManager());
lerp->setHandler(handler);
handler->clipBlendNodeManager()->appendNode(lerpId, lerp);
return lerp;
}
AdditiveClipBlend *createAdditiveClipBlend(Handler *handler)
{
auto additiveId = Qt3DCore::QNodeId::createId();
AdditiveClipBlend *additive = new AdditiveClipBlend();
setPeerId(additive, additiveId);
additive->setClipBlendNodeManager(handler->clipBlendNodeManager());
additive->setHandler(handler);
handler->clipBlendNodeManager()->appendNode(additiveId, additive);
return additive;
}
ClipBlendValue *createClipBlendValue(Handler *handler)
{
auto valueId = Qt3DCore::QNodeId::createId();
ClipBlendValue *value = new ClipBlendValue();
setPeerId(value, valueId);
value->setClipBlendNodeManager(handler->clipBlendNodeManager());
value->setHandler(handler);
handler->clipBlendNodeManager()->appendNode(valueId, value);
return value;
}
MeanBlendNode *createMeanBlendNode(Handler *handler)
{
auto id = Qt3DCore::QNodeId::createId();
MeanBlendNode *node = new MeanBlendNode();
setPeerId(node, id);
node->setClipBlendNodeManager(handler->clipBlendNodeManager());
node->setHandler(handler);
handler->clipBlendNodeManager()->appendNode(id, node);
return node;
}
Skeleton *createSkeleton(Handler *handler, int jointCount)
{
auto skeletonId = Qt3DCore::QNodeId::createId();
Skeleton *skeleton = handler->skeletonManager()->getOrCreateResource(skeletonId);
setPeerId(skeleton, skeletonId);
skeleton->setJointCount(jointCount);
return skeleton;
}
private Q_SLOTS:
void checkBuildPropertyMappings_data()
{
QTest::addColumn<Handler *>("handler");
QTest::addColumn<QVector<ChannelMapping *>>("channelMappings");
QTest::addColumn<QVector<ChannelNameAndType>>("channelNamesAndTypes");
QTest::addColumn<QVector<ComponentIndices>>("channelComponentIndices");
QTest::addColumn<QVector<QBitArray>>("sourceClipMask");
QTest::addColumn<QVector<MappingData>>("expectedResults");
// Single ChannelMapping
{
Handler *handler = new Handler();
auto channelMapping = createChannelMapping(handler,
QLatin1String("Location"),
Qt3DCore::QNodeId::createId(),
"translation",
static_cast<int>(QVariant::Vector3D),
3);
QVector<ChannelMapping *> channelMappings = { channelMapping };
// Create a few channels in the format description
ChannelNameAndType rotation = { QLatin1String("Rotation"),
static_cast<int>(QVariant::Quaternion),
4,
channelMapping->peerId() };
ChannelNameAndType location = { QLatin1String("Location"),
static_cast<int>(QVariant::Vector3D),
3,
channelMapping->peerId() };
ChannelNameAndType baseColor = { QLatin1String("BaseColor"),
static_cast<int>(QVariant::Vector3D),
3,
channelMapping->peerId() };
ChannelNameAndType metalness = { QLatin1String("Metalness"),
static_cast<int>(QVariant::Double),
1,
channelMapping->peerId() };
ChannelNameAndType roughness = { QLatin1String("Roughness"),
static_cast<int>(QVariant::Double),
1,
channelMapping->peerId() };
ChannelNameAndType morphTargetWeightsList = { QLatin1String("MorphTargetWeightsList"),
static_cast<int>(QVariant::List),
5,
channelMapping->peerId() };
ChannelNameAndType morphTargetWeightsVec = { QLatin1String("MorphTargetWeightsVec"),
qMetaTypeId<QVector<float>>(),
6,
channelMapping->peerId() };
ChannelNameAndType rgbColor = { QLatin1String("rgbColor"),
static_cast<int>(QVariant::Color),
3,
channelMapping->peerId() };
ChannelNameAndType rgbaColor = { QLatin1String("rgbaColor"),
static_cast<int>(QVariant::Color),
4,
channelMapping->peerId() };
QVector<ChannelNameAndType> channelNamesAndTypes
= { rotation, location, baseColor, metalness, roughness,
morphTargetWeightsList, morphTargetWeightsVec, rgbColor, rgbaColor };
// And the matching indices
ComponentIndices rotationIndices = { 0, 1, 2, 3 };
ComponentIndices locationIndices = { 4, 5, 6 };
ComponentIndices baseColorIndices = { 7, 8, 9 };
ComponentIndices metalnessIndices = { 10 };
ComponentIndices roughnessIndices = { 11 };
ComponentIndices morphTargetListIndices = { 12, 13, 14, 15, 16 };
ComponentIndices morphTargetVecIndices = { 17, 18, 19, 20, 21, 22 };
ComponentIndices rgbColorIndices = { 23, 24, 25 };
ComponentIndices rgbaColorIndices = { 26, 27, 28, 29 };
QVector<ComponentIndices> channelComponentIndices
= { rotationIndices, locationIndices, baseColorIndices,
metalnessIndices, roughnessIndices, morphTargetListIndices,
morphTargetVecIndices, rgbColorIndices, rgbaColorIndices };
QVector<QBitArray> sourceClipMask = { QBitArray(4, true),
QBitArray(3, true),
QBitArray(3, true),
QBitArray(1, true),
QBitArray(1, true),
QBitArray(5, true),
QBitArray(6, true),
QBitArray(3, true),
QBitArray(4, true),
};
MappingData expectedMapping;
expectedMapping.targetId = channelMapping->targetId();
expectedMapping.propertyName = channelMapping->propertyName();
expectedMapping.type = channelMapping->type();
expectedMapping.channelIndices = locationIndices;
QVector<MappingData> expectedResults = { expectedMapping };
QTest::newRow("single mapping")
<< handler
<< channelMappings
<< channelNamesAndTypes
<< channelComponentIndices
<< sourceClipMask
<< expectedResults;
}
// Multiple ChannelMappings
{
Handler *handler = new Handler();
auto locationMapping = createChannelMapping(handler,
QLatin1String("Location"),
Qt3DCore::QNodeId::createId(),
"translation",
static_cast<int>(QVariant::Vector3D),
3);
auto metalnessMapping = createChannelMapping(handler,
QLatin1String("Metalness"),
Qt3DCore::QNodeId::createId(),
"metalness",
static_cast<int>(QVariant::Double),
1);
auto baseColorMapping = createChannelMapping(handler,
QLatin1String("BaseColor"),
Qt3DCore::QNodeId::createId(),
"baseColor",
static_cast<int>(QVariant::Vector3D),
3);
auto roughnessMapping = createChannelMapping(handler,
QLatin1String("Roughness"),
Qt3DCore::QNodeId::createId(),
"roughness",
static_cast<int>(QVariant::Double),
1);
auto rotationMapping = createChannelMapping(handler,
QLatin1String("Rotation"),
Qt3DCore::QNodeId::createId(),
"rotation",
static_cast<int>(QVariant::Quaternion),
4);
auto morphTargetMapping = createChannelMapping(handler,
QLatin1String("MorphTargetWeights"),
Qt3DCore::QNodeId::createId(),
"weights",
static_cast<int>(QVariant::List),
5);
QVector<ChannelMapping *> channelMappings
= { locationMapping, metalnessMapping,
baseColorMapping, roughnessMapping,
rotationMapping, morphTargetMapping };
// Create a few channels in the format description
ChannelNameAndType rotation = { QLatin1String("Rotation"),
static_cast<int>(QVariant::Quaternion),
4,
rotationMapping->peerId() };
ChannelNameAndType location = { QLatin1String("Location"),
static_cast<int>(QVariant::Vector3D),
3,
locationMapping->peerId() };
ChannelNameAndType baseColor = { QLatin1String("BaseColor"),
static_cast<int>(QVariant::Vector3D),
3,
baseColorMapping->peerId() };
ChannelNameAndType metalness = { QLatin1String("Metalness"),
static_cast<int>(QVariant::Double),
1,
metalnessMapping->peerId() };
ChannelNameAndType roughness = { QLatin1String("Roughness"),
static_cast<int>(QVariant::Double),
1,
roughnessMapping->peerId() };
ChannelNameAndType morphTarget = { QLatin1String("MorphTargetWeights"),
static_cast<int>(QVariant::List),
5,
morphTargetMapping->peerId() };
QVector<ChannelNameAndType> channelNamesAndTypes
= { rotation, location, baseColor, metalness, roughness,
morphTarget };
// And the matching indices
ComponentIndices rotationIndices = { 0, 1, 2, 3 };
ComponentIndices locationIndices = { 4, 5, 6 };
ComponentIndices baseColorIndices = { 7, 8, 9 };
ComponentIndices metalnessIndices = { 10 };
ComponentIndices roughnessIndices = { 11 };
ComponentIndices morphTargetIndices = { 12, 13, 14, 15, 16 };
QVector<ComponentIndices> channelComponentIndices
= { rotationIndices, locationIndices, baseColorIndices,
metalnessIndices, roughnessIndices, morphTargetIndices };
QVector<QBitArray> sourceClipMask = { QBitArray(4, true),
QBitArray(3, true),
QBitArray(3, true),
QBitArray(1, true),
QBitArray(1, true),
QBitArray(5, true) };
MappingData expectedLocationMapping;
expectedLocationMapping.targetId = locationMapping->targetId();
expectedLocationMapping.propertyName = locationMapping->propertyName();
expectedLocationMapping.type = locationMapping->type();
expectedLocationMapping.channelIndices = locationIndices;
MappingData expectedMetalnessMapping;
expectedMetalnessMapping.targetId = metalnessMapping->targetId();
expectedMetalnessMapping.propertyName = metalnessMapping->propertyName();
expectedMetalnessMapping.type = metalnessMapping->type();
expectedMetalnessMapping.channelIndices = metalnessIndices;
MappingData expectedBaseColorMapping;
expectedBaseColorMapping.targetId = baseColorMapping->targetId();
expectedBaseColorMapping.propertyName = baseColorMapping->propertyName();
expectedBaseColorMapping.type = baseColorMapping->type();
expectedBaseColorMapping.channelIndices = baseColorIndices;
MappingData expectedRoughnessMapping;
expectedRoughnessMapping.targetId = roughnessMapping->targetId();
expectedRoughnessMapping.propertyName = roughnessMapping->propertyName();
expectedRoughnessMapping.type = roughnessMapping->type();
expectedRoughnessMapping.channelIndices = roughnessIndices;
MappingData expectedRotationMapping;
expectedRotationMapping.targetId = rotationMapping->targetId();
expectedRotationMapping.propertyName = rotationMapping->propertyName();
expectedRotationMapping.type = rotationMapping->type();
expectedRotationMapping.channelIndices = rotationIndices;
MappingData expectedMorphTargetMapping;
expectedMorphTargetMapping.targetId = morphTargetMapping->targetId();
expectedMorphTargetMapping.propertyName = morphTargetMapping->propertyName();
expectedMorphTargetMapping.type = morphTargetMapping->type();
expectedMorphTargetMapping.channelIndices = morphTargetIndices;
QVector<MappingData> expectedResults
= { expectedLocationMapping,
expectedMetalnessMapping,
expectedBaseColorMapping,
expectedRoughnessMapping,
expectedRotationMapping,
expectedMorphTargetMapping };
QTest::newRow("multiple mappings")
<< handler
<< channelMappings
<< channelNamesAndTypes
<< channelComponentIndices
<< sourceClipMask
<< expectedResults;
}
// Single skeleton mapping
{
Handler *handler = new Handler();
const int jointCount = 4;
auto skeleton = createSkeleton(handler, jointCount);
auto channelMapping = createChannelMapping(handler, skeleton->peerId());
QVector<ChannelMapping *> channelMappings = { channelMapping };
// Create a few channels in the format description
QVector<ChannelNameAndType> channelNamesAndTypes;
for (int i = 0; i < jointCount; ++i) {
ChannelNameAndType locationDescription = { QLatin1String("Location"),
static_cast<int>(QVariant::Vector3D),
3,
channelMapping->peerId() };
locationDescription.jointIndex = i;
locationDescription.jointTransformComponent = Translation;
channelNamesAndTypes.push_back(locationDescription);
ChannelNameAndType rotationDescription = { QLatin1String("Rotation"),
static_cast<int>(QVariant::Quaternion),
4,
channelMapping->peerId() };
rotationDescription.jointIndex = i;
rotationDescription.jointTransformComponent = Rotation;
channelNamesAndTypes.push_back(rotationDescription);
ChannelNameAndType scaleDescription = { QLatin1String("Scale"),
static_cast<int>(QVariant::Vector3D),
3,
channelMapping->peerId() };
scaleDescription.jointIndex = i;
scaleDescription.jointTransformComponent = Scale;
channelNamesAndTypes.push_back(scaleDescription);
}
// And the matching indices
QVector<ComponentIndices> channelComponentIndices;
channelComponentIndices.push_back({ 0, 1, 2 });
channelComponentIndices.push_back({ 3, 4, 5, 6 });
channelComponentIndices.push_back({ 7, 8, 9 });
channelComponentIndices.push_back({ 10, 11, 12 });
channelComponentIndices.push_back({ 13, 14, 15, 16 });
channelComponentIndices.push_back({ 17, 18, 19 });
channelComponentIndices.push_back({ 20, 21, 22 });
channelComponentIndices.push_back({ 23, 24, 25, 26 });
channelComponentIndices.push_back({ 27, 28, 29 });
channelComponentIndices.push_back({ 30, 31, 32 });
channelComponentIndices.push_back({ 33, 34, 35, 36 });
channelComponentIndices.push_back({ 37, 38, 39 });
QVector<QBitArray> sourceClipMask = { QBitArray(3, true),
QBitArray(4, true),
QBitArray(3, true),
QBitArray(3, true),
QBitArray(4, true),
QBitArray(3, true),
QBitArray(3, true),
QBitArray(4, true),
QBitArray(3, true),
QBitArray(3, true),
QBitArray(4, true),
QBitArray(3, true) };
QVector<MappingData> expectedResults;
int componentIndicesIndex = 0;
for (int i = 0; i < jointCount; ++i) {
MappingData locationMapping;
locationMapping.targetId = channelMapping->skeletonId();
locationMapping.propertyName = "translation";
locationMapping.type = static_cast<int>(QVariant::Vector3D);
locationMapping.channelIndices = channelComponentIndices[componentIndicesIndex++];
locationMapping.jointIndex = i;
MappingData rotationMapping;
rotationMapping.targetId = channelMapping->skeletonId();
rotationMapping.propertyName = "rotation";
rotationMapping.type = static_cast<int>(QVariant::Quaternion);
rotationMapping.channelIndices = channelComponentIndices[componentIndicesIndex++];
rotationMapping.jointIndex = i;
MappingData scaleMapping;
scaleMapping.targetId = channelMapping->skeletonId();
scaleMapping.propertyName = "scale";
scaleMapping.type = static_cast<int>(QVariant::Vector3D);
scaleMapping.channelIndices = channelComponentIndices[componentIndicesIndex++];
scaleMapping.jointIndex = i;
expectedResults << locationMapping << rotationMapping << scaleMapping;
}
QTest::newRow("single skeleton mapping")
<< handler
<< channelMappings
<< channelNamesAndTypes
<< channelComponentIndices
<< sourceClipMask
<< expectedResults;
}
}
void checkBuildPropertyMappings()
{
// GIVEN
QFETCH(Handler *, handler);
QFETCH(QVector<ChannelMapping *>, channelMappings);
QFETCH(QVector<ChannelNameAndType>, channelNamesAndTypes);
QFETCH(QVector<ComponentIndices>, channelComponentIndices);
QFETCH(QVector<QBitArray>, sourceClipMask);
QFETCH(QVector<MappingData>, expectedResults);
// WHEN
const QVector<MappingData> actualResults = buildPropertyMappings(channelMappings,
channelNamesAndTypes,
channelComponentIndices,
sourceClipMask);
// THEN
QCOMPARE(actualResults.size(), expectedResults.size());
for (int i = 0; i < actualResults.size(); ++i) {
const auto actualMapping = actualResults[i];
const auto expectedMapping = expectedResults[i];
QCOMPARE(actualMapping.targetId, expectedMapping.targetId);
QCOMPARE(actualMapping.jointIndex, expectedMapping.jointIndex);
QCOMPARE(actualMapping.propertyName, expectedMapping.propertyName);
QCOMPARE(actualMapping.type, expectedMapping.type);
QCOMPARE(actualMapping.channelIndices.size(), expectedMapping.channelIndices.size());
for (int j = 0; j < actualMapping.channelIndices.size(); ++j) {
QCOMPARE(actualMapping.channelIndices[j], expectedMapping.channelIndices[j]);
}
}
// Cleanup
delete handler;
}
void checkLocalTimeFromElapsedTime_data()
{
QTest::addColumn<double>("elapsedTime");
QTest::addColumn<double>("currentTime");
QTest::addColumn<double>("playbackRate");
QTest::addColumn<double>("duration");
QTest::addColumn<int>("loopCount");
QTest::addColumn<int>("currentLoop");
QTest::addColumn<double>("expectedLocalTime");
QTest::addColumn<int>("expectedCurrentLoop");
double elapsedTime;
double currentTime;
double playbackRate;
double duration;
int loopCount;
int currentLoop;
double expectedLocalTime;
int expectedCurrentLoop;
elapsedTime = 0.0;
currentTime = 0.0;
playbackRate = 1.0;
duration = 1.0;
loopCount = 1;
currentLoop = 0;
expectedLocalTime = 0.0;
expectedCurrentLoop = 0;
QTest::newRow("simple, t_current = 0, t_elapsed = 0, loop_current = 0")
<< elapsedTime << currentTime << playbackRate << duration << loopCount << currentLoop
<< expectedLocalTime << expectedCurrentLoop;
elapsedTime = 0.5;
currentTime = 0.0;
playbackRate = 1.0;
duration = 1.0;
loopCount = 1;
currentLoop = 0;
expectedLocalTime = 0.5;
expectedCurrentLoop = 0;
QTest::newRow("simple, t_current = 0, t_elapsed = 0.5, loop_current = 0")
<< elapsedTime << currentTime << playbackRate << duration << loopCount << currentLoop
<< expectedLocalTime << expectedCurrentLoop;
elapsedTime = 1.5;
currentTime = 0.0;
playbackRate = 1.0;
duration = 1.0;
loopCount = 1;
currentLoop = 0;
expectedLocalTime = 1.0;
expectedCurrentLoop = 0;
QTest::newRow("simple, t_current = 0, t_elapsed = 1.5, loop_current = 0")
<< elapsedTime << currentTime << playbackRate << duration << loopCount << currentLoop
<< expectedLocalTime << expectedCurrentLoop;
elapsedTime = 0.5;
currentTime = 0.6;
playbackRate = 1.0;
duration = 1.0;
loopCount = 1;
currentLoop = 0;
expectedLocalTime = 1.0;
expectedCurrentLoop = 0;
QTest::newRow("simple, t_current = 0.5, t_elapsed = 0.6, loop_current = 0")
<< elapsedTime << currentTime << playbackRate << duration << loopCount << currentLoop
<< expectedLocalTime << expectedCurrentLoop;
elapsedTime = 0.5;
currentTime = 0.6;
playbackRate = 1.0;
duration = 1.0;
loopCount = 2;
currentLoop = 0;
expectedLocalTime = 0.1;
expectedCurrentLoop = 1;
QTest::newRow("simple, t_current = 0.5, t_elapsed = 0.6, loop_current = 0, loop_count = 2")
<< elapsedTime << currentTime << playbackRate << duration << loopCount << currentLoop
<< expectedLocalTime << expectedCurrentLoop;
elapsedTime = 0.5;
currentTime = 0.6;
playbackRate = 1.0;
duration = 1.0;
loopCount = 2;
currentLoop = 1;
expectedLocalTime = 1.0;
expectedCurrentLoop = 1; // We clamp at end of final loop
QTest::newRow("simple, t_current = 0.5, t_elapsed = 0.6, loop_current = 1, loop_count = 2")
<< elapsedTime << currentTime << playbackRate << duration << loopCount << currentLoop
<< expectedLocalTime << expectedCurrentLoop;
elapsedTime = 0.5;
currentTime = 0.6;
playbackRate = 0.1;
duration = 1.0;
loopCount = 2;
currentLoop = 1;
expectedLocalTime = 0.65;
expectedCurrentLoop = 1;
QTest::newRow("simple, t_current = 0.5, t_elapsed = 0.6, loop_current = 1, loop_count = 2")
<< elapsedTime << currentTime << playbackRate << duration << loopCount << currentLoop
<< expectedLocalTime << expectedCurrentLoop;
}
void checkLocalTimeFromElapsedTime()
{
// GIVEN
QFETCH(double, elapsedTime);
QFETCH(double, currentTime);
QFETCH(double, playbackRate);
QFETCH(double, duration);
QFETCH(int, loopCount);
QFETCH(int, currentLoop);
QFETCH(double, expectedLocalTime);
QFETCH(int, expectedCurrentLoop);
// WHEN
int actualCurrentLoop = currentLoop;
double actualLocalTime = localTimeFromElapsedTime(currentTime,
elapsedTime,
playbackRate,
duration,
loopCount,
actualCurrentLoop);
// THEN
QCOMPARE(actualCurrentLoop, expectedCurrentLoop);
QCOMPARE(actualLocalTime, expectedLocalTime);
}
void checkPreparePropertyChanges_data()
{
QTest::addColumn<Qt3DCore::QNodeId>("animatorId");
QTest::addColumn<QVector<MappingData>>("mappingData");
QTest::addColumn<QVector<float>>("channelResults");
QTest::addColumn<AnimationRecord>("expectedChanges");
Qt3DCore::QNodeId animatorId;
QVector<MappingData> mappingData;
QVector<float> channelResults;
AnimationRecord expectedChanges;
// Single property, vec3
{
animatorId = Qt3DCore::QNodeId::createId();
MappingData mapping;
mapping.targetId = Qt3DCore::QNodeId::createId();
mapping.propertyName = "translation";
mapping.type = static_cast<int>(QVariant::Vector3D);
mapping.channelIndices = QVector<int>() << 0 << 1 << 2;
mappingData.push_back(mapping);
channelResults = QVector<float>() << 1.0f << 2.0f << 3.0f;
expectedChanges.normalizedTime = 1.1f; // Invalid
expectedChanges.finalFrame = false;
expectedChanges.targetChanges.push_back({mapping.targetId, mapping.propertyName, QVariant::fromValue(QVector3D(1.0f, 2.0f, 3.0f))});
QTest::newRow("vec3 translation, final = false")
<< animatorId << mappingData << channelResults << expectedChanges;
expectedChanges.normalizedTime = 1.0f;
expectedChanges.finalFrame = true;
QTest::newRow("vec3 translation, final = true, normalizedTime = 1.0f")
<< animatorId << mappingData << channelResults << expectedChanges;
mappingData.clear();
channelResults.clear();
expectedChanges.targetChanges.clear();
}
// Multiple properties, all vec3
{
animatorId = Qt3DCore::QNodeId::createId();
MappingData translationMapping;
translationMapping.targetId = Qt3DCore::QNodeId::createId();
translationMapping.propertyName = "translation";
translationMapping.type = static_cast<int>(QVariant::Vector3D);
translationMapping.channelIndices = QVector<int>() << 0 << 1 << 2;
mappingData.push_back(translationMapping);
MappingData scaleMapping;
scaleMapping.targetId = Qt3DCore::QNodeId::createId();
scaleMapping.propertyName = "scale";
scaleMapping.type = static_cast<int>(QVariant::Vector3D);
scaleMapping.channelIndices = QVector<int>() << 3 << 4 << 5;
mappingData.push_back(scaleMapping);
channelResults = QVector<float>() << 1.0f << 2.0f << 3.0f
<< 4.0f << 5.0f << 6.0f;
expectedChanges.finalFrame = false;
expectedChanges.normalizedTime = -0.1f; // Invalid
expectedChanges.targetChanges.push_back({translationMapping.targetId, translationMapping.propertyName, QVariant::fromValue(QVector3D(1.0f, 2.0f, 3.0f))});
expectedChanges.targetChanges.push_back({scaleMapping.targetId, scaleMapping.propertyName, QVariant::fromValue(QVector3D(4.0f, 5.0f, 6.0f))});
QTest::newRow("vec3 translation, vec3 scale, final = false")
<< animatorId << mappingData << channelResults << expectedChanges;
expectedChanges.normalizedTime = 0.5f;
expectedChanges.finalFrame = true;
QTest::newRow("vec3 translation, vec3 scale, final = true")
<< animatorId << mappingData << channelResults << expectedChanges;
mappingData.clear();
channelResults.clear();
expectedChanges.targetChanges.clear();
}
// Single property, double
{
animatorId = Qt3DCore::QNodeId::createId();
MappingData mapping;
mapping.targetId = Qt3DCore::QNodeId::createId();
mapping.propertyName = "mass";
mapping.type = static_cast<int>(QVariant::Double);
mapping.channelIndices = QVector<int>() << 0;
mappingData.push_back(mapping);
channelResults = QVector<float>() << 3.5f;
expectedChanges.finalFrame = false;
expectedChanges.normalizedTime = -1.0f; // Invalid
expectedChanges.targetChanges.push_back({mapping.targetId, mapping.propertyName, QVariant::fromValue(3.5f)});
QTest::newRow("double mass")
<< animatorId << mappingData << channelResults << expectedChanges;
mappingData.clear();
channelResults.clear();
expectedChanges.targetChanges.clear();
}
// Single property, vec2
{
animatorId = Qt3DCore::QNodeId::createId();
MappingData mapping;
mapping.targetId = Qt3DCore::QNodeId::createId();
mapping.propertyName = "pos";
mapping.type = static_cast<int>(QVariant::Vector2D);
mapping.channelIndices = QVector<int>() << 0 << 1;
mappingData.push_back(mapping);
channelResults = QVector<float>() << 2.0f << 1.0f;
expectedChanges.finalFrame = false;
expectedChanges.normalizedTime = 1.1f; // Invalid
expectedChanges.targetChanges.push_back({mapping.targetId, mapping.propertyName, QVariant::fromValue(QVector2D(2.0f, 1.0f))});
QTest::newRow("vec2 pos")
<< animatorId << mappingData << channelResults << expectedChanges;
mappingData.clear();
channelResults.clear();
expectedChanges.targetChanges.clear();
}
// Single property, vec4
{
animatorId = Qt3DCore::QNodeId::createId();
MappingData mapping;
mapping.targetId = Qt3DCore::QNodeId::createId();
mapping.propertyName = "foo";
mapping.type = static_cast<int>(QVariant::Vector4D);
mapping.channelIndices = QVector<int>() << 0 << 1 << 2 << 3;
mappingData.push_back(mapping);
channelResults = QVector<float>() << 4.0f << 3.0f << 2.0f << 1.0f;
expectedChanges.finalFrame = false;
expectedChanges.normalizedTime = 1.1f; // Invalid
expectedChanges.targetChanges.push_back({mapping.targetId, mapping.propertyName, QVariant::fromValue(QVector4D(4.0f, 3.0f, 2.0f, 1.0f))});
QTest::newRow("vec4 foo")
<< animatorId << mappingData << channelResults << expectedChanges;
mappingData.clear();
channelResults.clear();
expectedChanges.targetChanges.clear();
}
// Single property, quaternion
{
animatorId = Qt3DCore::QNodeId::createId();
MappingData mapping;
mapping.targetId = Qt3DCore::QNodeId::createId();
mapping.propertyName = "rotation";
mapping.type = static_cast<int>(QVariant::Quaternion);
mapping.channelIndices = QVector<int>() << 0 << 1 << 2 << 3;
mappingData.push_back(mapping);
channelResults = QVector<float>() << 1.0f << 0.0f << 0.0f << 1.0f;
expectedChanges.finalFrame = false;
expectedChanges.normalizedTime = -0.1f; // Invalid
expectedChanges.targetChanges.push_back({mapping.targetId, mapping.propertyName, QVariant::fromValue(QQuaternion(1.0f, 0.0f, 0.0f, 1.0f).normalized())});
QTest::newRow("quaternion rotation")
<< animatorId << mappingData << channelResults << expectedChanges;
mappingData.clear();
channelResults.clear();
expectedChanges.targetChanges.clear();
}
// Single property, QColor
{
animatorId = Qt3DCore::QNodeId::createId();
MappingData mapping;
mapping.targetId = Qt3DCore::QNodeId::createId();
mapping.propertyName = "color";
mapping.type = static_cast<int>(QVariant::Color);
mapping.channelIndices = QVector<int>() << 0 << 1 << 2;
mappingData.push_back(mapping);
channelResults = QVector<float>() << 0.5f << 0.4f << 0.3f;
expectedChanges.finalFrame = false;
expectedChanges.normalizedTime = 1.1f; // Invalid
expectedChanges.targetChanges.push_back({mapping.targetId, mapping.propertyName, QVariant::fromValue(QColor::fromRgbF(0.5f, 0.4f, 0.3f))});
QTest::newRow("QColor rgb color")
<< animatorId << mappingData << channelResults << expectedChanges;
mappingData.clear();
channelResults.clear();
expectedChanges.targetChanges.clear();
}
// Single property, QColor
{
animatorId = Qt3DCore::QNodeId::createId();
MappingData mapping;
mapping.targetId = Qt3DCore::QNodeId::createId();
mapping.propertyName = "color";
mapping.type = static_cast<int>(QVariant::Color);
mapping.channelIndices = QVector<int>() << 0 << 1 << 2 << 3;
mappingData.push_back(mapping);
channelResults = QVector<float>() << 0.5f << 0.4f << 0.3f << 0.2f;
expectedChanges.finalFrame = false;
expectedChanges.normalizedTime = 1.1f; // Invalid
expectedChanges.targetChanges.push_back({mapping.targetId, mapping.propertyName, QVariant::fromValue(QColor::fromRgbF(0.5f, 0.4f, 0.3f, 0.2f))});
QTest::newRow("QColor rgba color")
<< animatorId << mappingData << channelResults << expectedChanges;
mappingData.clear();
channelResults.clear();
expectedChanges.targetChanges.clear();
}
// Single property, QVariantList
{
animatorId = Qt3DCore::QNodeId::createId();
MappingData mapping;
mapping.targetId = Qt3DCore::QNodeId::createId();
mapping.propertyName = "weights";
mapping.type = static_cast<int>(QVariant::List);
mapping.channelIndices = QVector<int>() << 0 << 1 << 2 << 3 << 4 << 5 << 6;
mappingData.push_back(mapping);
channelResults = QVector<float>() << 0.5f << 0.4f << 0.3f << 0.0f << 1.0f << 0.6f << 0.9f;
expectedChanges.finalFrame = false;
expectedChanges.normalizedTime = 1.1f; // Invalid
QVariantList expectedValue = QVariantList() << 0.5f << 0.4f << 0.3f << 0.0f << 1.0f << 0.6f << 0.9f;
expectedChanges.targetChanges.push_back({mapping.targetId, mapping.propertyName, QVariant::fromValue(expectedValue)});
QTest::newRow("QVariantList weights")
<< animatorId << mappingData << channelResults << expectedChanges;
mappingData.clear();
channelResults.clear();
expectedChanges.targetChanges.clear();
}
}
void checkPreparePropertyChanges()
{
// GIVEN
QFETCH(Qt3DCore::QNodeId, animatorId);
QFETCH(QVector<MappingData>, mappingData);
QFETCH(QVector<float>, channelResults);
QFETCH(AnimationRecord, expectedChanges);
// WHEN
AnimationRecord actualChanges = prepareAnimationRecord(animatorId, mappingData, channelResults,
expectedChanges.finalFrame, expectedChanges.normalizedTime);
// THEN
QCOMPARE(actualChanges.targetChanges.size(), expectedChanges.targetChanges.size());
for (int i = 0; i < actualChanges.targetChanges.size(); ++i) {
const auto &expectedChange = expectedChanges.targetChanges[i];
const auto &actualChange = actualChanges.targetChanges[i];
QCOMPARE(actualChange.targetId, expectedChange.targetId);
QCOMPARE(actualChange.propertyName, expectedChange.propertyName);
QCOMPARE(actualChange.value, expectedChange.value);
}
}
void checkPrepareCallbacks_data()
{
QTest::addColumn<QVector<MappingData>>("mappingData");
QTest::addColumn<QVector<float>>("channelResults");
QTest::addColumn<QVector<AnimationCallbackAndValue> >("expectedValues");
QVector<MappingData> mappingData;
QVector<float> channelResults;
QVector<AnimationCallbackAndValue> expectedValues;
// vec3
{
DummyCallback callback; // safe since the object is never used, just the address
MappingData mapping;
mapping.targetId = Qt3DCore::QNodeId::createId();
mapping.propertyName = "translation";
mapping.type = static_cast<int>(QVariant::Vector3D);
mapping.channelIndices = QVector<int>() << 0 << 1 << 2;
mapping.callback = &callback;
mapping.callbackFlags = {};
mappingData.push_back(mapping);
channelResults = QVector<float>() << 1.0f << 2.0f << 3.0f;
AnimationCallbackAndValue cbv;
cbv.callback = mapping.callback;
cbv.flags = mapping.callbackFlags;
cbv.value = QVariant::fromValue<QVector3D>(QVector3D(1.0f, 2.0f, 3.0f));
expectedValues.push_back(cbv);
QTest::newRow("vec3 translation, no flags") << mappingData << channelResults << expectedValues;
mappingData.clear();
channelResults.clear();
expectedValues.clear();
}
// double
{
DummyCallback callback;
MappingData mapping;
mapping.targetId = Qt3DCore::QNodeId::createId();
mapping.propertyName = "something";
mapping.type = static_cast<int>(QVariant::Double);
mapping.channelIndices = QVector<int>() << 0;
mapping.callback = &callback;
mapping.callbackFlags = {};
mappingData.push_back(mapping);
channelResults = QVector<float>() << 1.0f;
AnimationCallbackAndValue cbv;
cbv.callback = mapping.callback;
cbv.flags = mapping.callbackFlags;
cbv.value = QVariant(double(1.0));
expectedValues.push_back(cbv);
QTest::newRow("double, no flags") << mappingData << channelResults << expectedValues;
mappingData.clear();
channelResults.clear();
expectedValues.clear();
}
// float, set a flag
{
DummyCallback callback;
MappingData mapping;
mapping.targetId = Qt3DCore::QNodeId::createId();
mapping.propertyName = "opacity";
mapping.type = static_cast<int>(QMetaType::Float);
mapping.channelIndices = QVector<int>() << 0;
mapping.callback = &callback;
mapping.callbackFlags = Qt3DAnimation::QAnimationCallback::OnThreadPool;
mappingData.push_back(mapping);
channelResults = QVector<float>() << 0.5f;
AnimationCallbackAndValue cbv;
cbv.callback = mapping.callback;
cbv.flags = mapping.callbackFlags;
cbv.value = QVariant(float(0.5f));
expectedValues.push_back(cbv);
QTest::newRow("float, OnThreadPool") << mappingData << channelResults << expectedValues;
mappingData.clear();
channelResults.clear();
expectedValues.clear();
}
}
void checkPrepareCallbacks()
{
// GIVEN
QFETCH(QVector<MappingData>, mappingData);
QFETCH(QVector<float>, channelResults);
QFETCH(QVector<AnimationCallbackAndValue>, expectedValues);
// WHEN
QVector<AnimationCallbackAndValue> callbacks = prepareCallbacks(mappingData, channelResults);
// THEN
QCOMPARE(callbacks.size(), expectedValues.size());
for (int i = 0; i < callbacks.size(); ++i) {
auto expected = expectedValues[i];
auto actual = callbacks[i];
QCOMPARE(actual.callback, expected.callback);
QCOMPARE(actual.flags, expected.flags);
QCOMPARE(actual.value, expected.value);
}
}
void checkEvaluateClipAtLocalTime_data()
{
QTest::addColumn<Handler *>("handler");
QTest::addColumn<AnimationClip *>("clip");
QTest::addColumn<float>("localTime");
QTest::addColumn<ClipResults>("expectedResults");
Handler *handler;
AnimationClip *clip;
float localTime;
ClipResults expectedResults;
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
localTime = 0.0f;
expectedResults = QVector<float>() << 0.0f << 0.0f << 0.0f;
QTest::newRow("clip1.json, t = 0.0")
<< handler << clip << localTime << expectedResults;
expectedResults.clear();
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
localTime = clip->duration();
expectedResults = QVector<float>() << 5.0f << 0.0f << 0.0f;
QTest::newRow("clip1.json, t = duration")
<< handler << clip << localTime << expectedResults;
expectedResults.clear();
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
localTime = clip->duration() / 2.0f;
expectedResults = QVector<float>() << 2.5f << 0.0f << 0.0f;
QTest::newRow("clip1.json, t = duration/2")
<< handler << clip << localTime << expectedResults;
expectedResults.clear();
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip2.json"));
localTime = 0.0f;
expectedResults = QVector<float>()
<< 0.0f << 0.0f << 0.0f // Translation
<< 1.0f << 0.0f << 0.0f << 0.0f; // Rotation
QTest::newRow("clip2.json, t = 0.0")
<< handler << clip << localTime << expectedResults;
expectedResults.clear();
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip2.json"));
localTime = clip->duration();
expectedResults = QVector<float>()
<< 5.0f << 0.0f << 0.0f // Translation
<< 0.0f << 0.0f << -1.0f << 0.0f; // Rotation
QTest::newRow("clip2.json, t = duration")
<< handler << clip << localTime << expectedResults;
expectedResults.clear();
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip2.json"));
localTime = clip->duration() / 2.0f;
expectedResults = QVector<float>()
<< 2.5f << 0.0f << 0.0f // Translation
<< 0.5f << 0.0f << -0.5f << 0.0f; // Rotation
QTest::newRow("clip2.json, t = duration/2")
<< handler << clip << localTime << expectedResults;
expectedResults.clear();
}
{
// a clip with linear interpolation
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip4.json"));
localTime = clip->duration();
expectedResults = QVector<float>() << 5.0 << -2.0f << 6.0f;
QTest::newRow("clip4.json, linear, t = duration")
<< handler << clip << localTime << expectedResults;
expectedResults.clear();
}
{
// a clip with linear interpolation
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip4.json"));
localTime = clip->duration() / 2.0f;
expectedResults = QVector<float>() << 2.5f << -1.0f << 3.0f;
QTest::newRow("clip4.json, linear, t = duration/2")
<< handler << clip << localTime << expectedResults;
expectedResults.clear();
}
{
// a clip with slerp interpolation
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip6.json"));
localTime = clip->duration() / 2.0f;
expectedResults = QVector<float>() << 0.923822f << 0.382626f << 0.0f << 0.0f;
QTest::newRow("clip6.json, slerp, t = duration/2")
<< handler << clip << localTime << expectedResults;
expectedResults.clear();
}
}
void checkEvaluateClipAtLocalTime()
{
// GIVEN
QFETCH(Handler *, handler);
QFETCH(AnimationClip *, clip);
QFETCH(float, localTime);
QFETCH(ClipResults, expectedResults);
// WHEN
ClipResults actualResults = evaluateClipAtLocalTime(clip, localTime);
// THEN
QCOMPARE(actualResults.size(), expectedResults.size());
for (int i = 0; i < actualResults.size(); ++i) {
auto actual = actualResults[i];
auto expected = expectedResults[i];
QVERIFY(fuzzyCompare(actual, expected) == true);
}
// Cleanup
delete handler;
}
void checkEvaluateClipAtPhase_data()
{
QTest::addColumn<Handler *>("handler");
QTest::addColumn<AnimationClip *>("clip");
QTest::addColumn<float>("phase");
QTest::addColumn<ClipResults>("expectedResults");
Handler *handler;
AnimationClip *clip;
float phase;
ClipResults expectedResults;
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
phase = 0.0f;
expectedResults = QVector<float>() << 0.0f << 0.0f << 0.0f;
QTest::newRow("clip1.json, phi = 0.0")
<< handler << clip << phase << expectedResults;
expectedResults.clear();
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
phase = 1.0f;
expectedResults = QVector<float>() << 5.0f << 0.0f << 0.0f;
QTest::newRow("clip1.json, phi = 1.0")
<< handler << clip << phase << expectedResults;
expectedResults.clear();
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
phase = 0.5f;
expectedResults = QVector<float>() << 2.5f << 0.0f << 0.0f;
QTest::newRow("clip1.json, phi = 0.5")
<< handler << clip << phase << expectedResults;
expectedResults.clear();
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip2.json"));
phase = 0.0f;
expectedResults = QVector<float>()
<< 0.0f << 0.0f << 0.0f // Translation
<< 1.0f << 0.0f << 0.0f << 0.0f; // Rotation
QTest::newRow("clip2.json, phi = 0.0")
<< handler << clip << phase << expectedResults;
expectedResults.clear();
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip2.json"));
phase = 1.0f;
expectedResults = QVector<float>()
<< 5.0f << 0.0f << 0.0f // Translation
<< 0.0f << 0.0f << -1.0f << 0.0f; // Rotation
QTest::newRow("clip2.json, t = 1.0")
<< handler << clip << phase << expectedResults;
expectedResults.clear();
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip2.json"));
phase = 0.5f;
expectedResults = QVector<float>()
<< 2.5f << 0.0f << 0.0f // Translation
<< 0.5f << 0.0f << -0.5f << 0.0f; // Rotation
QTest::newRow("clip2.json, phi = 0.5")
<< handler << clip << phase << expectedResults;
expectedResults.clear();
}
}
void checkEvaluateClipAtPhase()
{
// GIVEN
QFETCH(Handler *, handler);
QFETCH(AnimationClip *, clip);
QFETCH(float, phase);
QFETCH(ClipResults, expectedResults);
// WHEN
ClipResults actualResults = evaluateClipAtPhase(clip, phase);
// THEN
QCOMPARE(actualResults.size(), expectedResults.size());
for (int i = 0; i < actualResults.size(); ++i) {
auto actual = actualResults[i];
auto expected = expectedResults[i];
QVERIFY(fuzzyCompare(actual, expected) == true);
}
// Cleanup
delete handler;
}
void checkChannelComponentsToIndicesHelper_data()
{
QTest::addColumn<Channel>("channel");
QTest::addColumn<int>("dataType");
QTest::addColumn<int>("expectedChannelComponentCount");
QTest::addColumn<int>("offset");
QTest::addColumn<QVector<char>>("suffixes");
QTest::addColumn<QVector<int>>("expectedResults");
Channel channel;
int dataType;
int expectedChannelComponentCount;
int offset;
QVector<char> suffixes;
QVector<int> expectedResults;
// already sorted vec3, no component names, with and without offset
{
channel = Channel();
channel.name = QLatin1String("Location");
channel.channelComponents.resize(3);
// leave 'name' empty
dataType = static_cast<int>(QVariant::Vector3D);
expectedChannelComponentCount = 3;
offset = 0;
// suffixes expected to be ignored
expectedResults = (QVector<int>() << 0 << 1 << 2);
QTest::newRow("vec3 location, pre-sorted, no component names, offset = 0")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
expectedResults.clear();
offset = 4;
expectedResults = (QVector<int>() << 4 << 5 << 6);
QTest::newRow("vec3 location, pre-sorted, no component names, offset = 4")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
expectedResults.clear();
}
// vec3 with and without offset
{
channel = Channel();
channel.name = QLatin1String("Location");
channel.channelComponents.resize(3);
channel.channelComponents[0].name = QLatin1String("Location X");
channel.channelComponents[1].name = QLatin1String("Location Y");
channel.channelComponents[2].name = QLatin1String("Location Z");
dataType = static_cast<int>(QVariant::Vector3D);
expectedChannelComponentCount = 3;
offset = 0;
suffixes = (QVector<char>() << 'X' << 'Y' << 'Z' << 'W');
expectedResults = (QVector<int>() << 0 << 1 << 2);
QTest::newRow("vec3 location, offset = 0")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
expectedResults.clear();
offset = 4;
expectedResults = (QVector<int>() << 4 << 5 << 6);
QTest::newRow("vec3 location, offset = 4")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
suffixes.clear();
expectedResults.clear();
}
// vec2 with and without offset
{
channel = Channel();
channel.name = QLatin1String("pos");
channel.channelComponents.resize(2);
channel.channelComponents[0].name = QLatin1String("pos X");
channel.channelComponents[1].name = QLatin1String("pos Y");
dataType = static_cast<int>(QVariant::Vector2D);
expectedChannelComponentCount = 2;
offset = 0;
suffixes = (QVector<char>() << 'X' << 'Y' << 'Z' << 'W');
expectedResults = (QVector<int>() << 0 << 1);
QTest::newRow("vec2 pos, offset = 0")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
expectedResults.clear();
offset = 2;
expectedResults = (QVector<int>() << 2 << 3);
QTest::newRow("vec2 pos, offset = 2")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
suffixes.clear();
expectedResults.clear();
}
// vec4 with and without offset
{
channel = Channel();
channel.name = QLatin1String("foo");
channel.channelComponents.resize(4);
channel.channelComponents[0].name = QLatin1String("foo X");
channel.channelComponents[1].name = QLatin1String("foo Y");
channel.channelComponents[2].name = QLatin1String("foo Z");
channel.channelComponents[3].name = QLatin1String("foo W");
dataType = static_cast<int>(QVariant::Vector4D);
expectedChannelComponentCount = 4;
offset = 0;
suffixes = (QVector<char>() << 'X' << 'Y' << 'Z' << 'W');
expectedResults = (QVector<int>() << 0 << 1 << 2 << 3);
QTest::newRow("vec4 foo, offset = 0")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
expectedResults.clear();
offset = 10;
expectedResults = (QVector<int>() << 10 << 11 << 12 << 13);
QTest::newRow("vec4 foo, offset = 10")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
suffixes.clear();
expectedResults.clear();
}
// double with and without offset
{
channel = Channel();
channel.name = QLatin1String("foo");
channel.channelComponents.resize(1);
channel.channelComponents[0].name = QLatin1String("Mass X");
dataType = static_cast<int>(QVariant::Double);
expectedChannelComponentCount = 1;
offset = 0;
suffixes = (QVector<char>() << 'X' << 'Y' << 'Z' << 'W');
expectedResults = (QVector<int>() << 0);
QTest::newRow("double Mass, offset = 0")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
expectedResults.clear();
offset = 5;
expectedResults = (QVector<int>() << 5);
QTest::newRow("double Mass, offset = 5")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
suffixes.clear();
expectedResults.clear();
}
// quaternion with and without offset
{
channel = Channel();
channel.name = QLatin1String("Rotation");
channel.channelComponents.resize(4);
channel.channelComponents[0].name = QLatin1String("Rotation W");
channel.channelComponents[1].name = QLatin1String("Rotation X");
channel.channelComponents[2].name = QLatin1String("Rotation Y");
channel.channelComponents[3].name = QLatin1String("Rotation Z");
dataType = static_cast<int>(QVariant::Quaternion);
expectedChannelComponentCount = 4;
offset = 0;
suffixes = (QVector<char>() << 'W' << 'X' << 'Y' << 'Z');
expectedResults = (QVector<int>() << 0 << 1 << 2 << 3);
QTest::newRow("quaternion Rotation, offset = 0")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
expectedResults.clear();
offset = 10;
expectedResults = (QVector<int>() << 10 << 11 << 12 << 13);
QTest::newRow("quaternion Rotation, offset = 10")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
suffixes.clear();
expectedResults.clear();
}
// quaternion with and without offset, randomized
{
channel = Channel();
channel.name = QLatin1String("Rotation");
channel.channelComponents.resize(4);
channel.channelComponents[0].name = QLatin1String("Rotation X");
channel.channelComponents[1].name = QLatin1String("Rotation W");
channel.channelComponents[2].name = QLatin1String("Rotation Z");
channel.channelComponents[3].name = QLatin1String("Rotation Y");
dataType = static_cast<int>(QVariant::Quaternion);
expectedChannelComponentCount = 4;
offset = 0;
suffixes = (QVector<char>() << 'W' << 'X' << 'Y' << 'Z');
expectedResults = (QVector<int>() << 1 << 0 << 3 << 2);
QTest::newRow("quaternion Rotation, offset = 0, randomized")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
expectedResults.clear();
offset = 10;
expectedResults = (QVector<int>() << 11 << 10 << 13 << 12);
QTest::newRow("quaternion Rotation, offset = 10, randomized")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
suffixes.clear();
expectedResults.clear();
}
// color with and without offset 3 components
{
channel = Channel();
channel.name = QLatin1String("Color");
channel.channelComponents.resize(3);
channel.channelComponents[0].name = QLatin1String("Color R");
channel.channelComponents[1].name = QLatin1String("Color G");
channel.channelComponents[2].name = QLatin1String("Color B");
dataType = static_cast<int>(QVariant::Color);
expectedChannelComponentCount = 3;
offset = 0;
suffixes = (QVector<char>() << 'R' << 'G' << 'B');
expectedResults = (QVector<int>() << 0 << 1 << 2);
QTest::newRow("QColor RGB Color, offset = 0")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
expectedResults.clear();
offset = 10;
expectedResults = (QVector<int>() << 10 << 11 << 12);
QTest::newRow("QColor RGB Color, offset = 10")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
suffixes.clear();
expectedResults.clear();
}
// color with and without offset 4 components
{
channel = Channel();
channel.name = QLatin1String("Color");
channel.channelComponents.resize(4);
channel.channelComponents[0].name = QLatin1String("Color R");
channel.channelComponents[1].name = QLatin1String("Color G");
channel.channelComponents[2].name = QLatin1String("Color B");
channel.channelComponents[3].name = QLatin1String("Color A");
dataType = static_cast<int>(QVariant::Color);
expectedChannelComponentCount = 4;
offset = 0;
suffixes = (QVector<char>() << 'R' << 'G' << 'B' << 'A');
expectedResults = (QVector<int>() << 0 << 1 << 2 << 3);
QTest::newRow("QColor RGBA Color, offset = 0")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
expectedResults.clear();
offset = 10;
expectedResults = (QVector<int>() << 10 << 11 << 12 << 13);
QTest::newRow("QColor RGBA Color, offset = 10")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
suffixes.clear();
expectedResults.clear();
}
// weights as list with and without offset
{
channel = Channel();
channel.name = QLatin1String("MorphWeights");
channel.channelComponents.resize(6);
// leave channel component names empty
dataType = static_cast<int>(QVariant::List);
expectedChannelComponentCount = 6;
offset = 0;
// suffixes expected to be ignored
expectedResults = (QVector<int>() << 0 << 1 << 2 << 3 << 4 << 5);
QTest::newRow("MorphWeights List count = 6, offset = 0")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
expectedResults.clear();
offset = 10;
expectedResults = (QVector<int>() << 10 << 11 << 12 << 13 << 14 << 15);
QTest::newRow("MorphWeights List count = 6, offset = 10")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
suffixes.clear();
expectedResults.clear();
}
// weights as vec of float with and without offset
{
channel = Channel();
channel.name = QLatin1String("MorphWeights");
channel.channelComponents.resize(6);
// leave channel component names empty
dataType = qMetaTypeId<QVector<float>>();
expectedChannelComponentCount = 6;
offset = 0;
// suffixes expected to be ignored
expectedResults = (QVector<int>() << 0 << 1 << 2 << 3 << 4 << 5);
QTest::newRow("MorphWeights Vec count = 6, offset = 0")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
expectedResults.clear();
offset = 10;
expectedResults = (QVector<int>() << 10 << 11 << 12 << 13 << 14 << 15);
QTest::newRow("MorphWeights Vec count = 6, offset = 10")
<< channel << dataType << expectedChannelComponentCount
<< offset << suffixes << expectedResults;
suffixes.clear();
expectedResults.clear();
}
}
void checkChannelComponentsToIndicesHelper()
{
// GIVEN
QFETCH(Channel, channel);
QFETCH(int, offset);
QFETCH(int, expectedChannelComponentCount);
QFETCH(QVector<char>, suffixes);
QFETCH(QVector<int>, expectedResults);
// WHEN
QVector<int> actualResults
= channelComponentsToIndicesHelper(channel, expectedChannelComponentCount,
offset, suffixes);
// THEN
QCOMPARE(actualResults.size(), expectedResults.size());
for (int i = 0; i < actualResults.size(); ++i) {
QCOMPARE(actualResults[i], expectedResults[i]);
}
}
void checkChannelComponentsToIndices_data()
{
QTest::addColumn<Channel>("channel");
QTest::addColumn<int>("dataType");
QTest::addColumn<int>("componentCount");
QTest::addColumn<int>("offset");
QTest::addColumn<QVector<int>>("expectedResults");
Channel channel;
int dataType;
int componentCount;
int offset;
QVector<int> expectedResults;
// Quaternion
{
channel = Channel();
channel.name = QLatin1String("Rotation");
channel.channelComponents.resize(4);
channel.channelComponents[0].name = QLatin1String("Rotation W");
channel.channelComponents[1].name = QLatin1String("Rotation X");
channel.channelComponents[2].name = QLatin1String("Rotation Y");
channel.channelComponents[3].name = QLatin1String("Rotation Z");
dataType = static_cast<int>(QVariant::Quaternion);
componentCount = 4;
offset = 0;
expectedResults = (QVector<int>() << 0 << 1 << 2 << 3);
QTest::newRow("quaternion Rotation, offset = 0")
<< channel << dataType << componentCount << offset << expectedResults;
expectedResults.clear();
offset = 10;
expectedResults = (QVector<int>() << 10 << 11 << 12 << 13);
QTest::newRow("quaternion Rotation, offset = 10")
<< channel << dataType << componentCount << offset << expectedResults;
expectedResults.clear();
}
// vec3 with and without offset
{
channel = Channel();
channel.name = QLatin1String("Location");
channel.channelComponents.resize(3);
channel.channelComponents[0].name = QLatin1String("Location X");
channel.channelComponents[1].name = QLatin1String("Location Y");
channel.channelComponents[2].name = QLatin1String("Location Z");
dataType = static_cast<int>(QVariant::Vector3D);
componentCount = 3;
offset = 0;
expectedResults = (QVector<int>() << 0 << 1 << 2);
QTest::newRow("vec3 location, offset = 0")
<< channel << dataType << componentCount << offset << expectedResults;
expectedResults.clear();
offset = 4;
expectedResults = (QVector<int>() << 4 << 5 << 6);
QTest::newRow("vec3 location, offset = 4")
<< channel << dataType << componentCount << offset << expectedResults;
expectedResults.clear();
}
// QColor
{
channel = Channel();
channel.name = QLatin1String("Color");
channel.channelComponents.resize(3);
channel.channelComponents[0].name = QLatin1String("Color R");
channel.channelComponents[1].name = QLatin1String("Color G");
channel.channelComponents[2].name = QLatin1String("Color B");
dataType = static_cast<int>(QVariant::Color);
componentCount = 3;
offset = 0;
expectedResults = (QVector<int>() << 0 << 1 << 2);
QTest::newRow("QColor RGB Color, offset = 0")
<< channel << dataType << componentCount << offset << expectedResults;
expectedResults.clear();
offset = 10;
expectedResults = (QVector<int>() << 10 << 11 << 12);
QTest::newRow("QColor RGB Color, offset = 10")
<< channel << dataType << componentCount << offset << expectedResults;
expectedResults.clear();
}
{
channel = Channel();
channel.name = QLatin1String("Color");
channel.channelComponents.resize(4);
channel.channelComponents[0].name = QLatin1String("Color R");
channel.channelComponents[1].name = QLatin1String("Color G");
channel.channelComponents[2].name = QLatin1String("Color B");
channel.channelComponents[3].name = QLatin1String("Color A");
dataType = static_cast<int>(QVariant::Color);
componentCount = 4;
offset = 0;
expectedResults = (QVector<int>() << 0 << 1 << 2 << 3);
QTest::newRow("QColor RGBA Color, offset = 0")
<< channel << dataType << componentCount << offset << expectedResults;
expectedResults.clear();
offset = 10;
expectedResults = (QVector<int>() << 10 << 11 << 12 << 13);
QTest::newRow("QColor RGBA Color, offset = 10")
<< channel << dataType << componentCount << offset << expectedResults;
expectedResults.clear();
}
}
void checkChannelComponentsToIndices()
{
QFETCH(Channel, channel);
QFETCH(int, dataType);
QFETCH(int, componentCount);
QFETCH(int, offset);
QFETCH(QVector<int>, expectedResults);
// WHEN
QVector<int> actualResults
= channelComponentsToIndices(channel, dataType, componentCount, offset);
// THEN
QCOMPARE(actualResults.size(), expectedResults.size());
for (int i = 0; i < actualResults.size(); ++i) {
QCOMPARE(actualResults[i], expectedResults[i]);
}
}
void checkEvaluationDataForClip_data()
{
QTest::addColumn<Handler *>("handler");
QTest::addColumn<AnimationClip *>("clip");
QTest::addColumn<AnimatorEvaluationData>("animatorData");
QTest::addColumn<ClipEvaluationData>("expectedClipData");
Handler *handler;
AnimationClip *clip;
AnimatorEvaluationData animatorData;
ClipEvaluationData clipData;
auto* clock = new Clock;
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
const qint64 globalStartTimeNS = 0;
const int loops = 1;
auto animator = createClipAnimator(handler, globalStartTimeNS, loops);
animator->setCurrentLoop(0);
clipData.currentLoop = animator->currentLoop();
const qint64 elapsedTimeNS = 0;
animatorData = evaluationDataForAnimator(animator, clock, elapsedTimeNS); // Tested elsewhere
clipData.localTime = localTimeFromElapsedTime(animatorData.currentTime,
animatorData.elapsedTime,
animatorData.playbackRate,
clip->duration(),
animatorData.loopCount,
clipData.currentLoop); // Tested elsewhere
clipData.isFinalFrame = false;
QTest::newRow("clip1.json, globalTime = 0")
<< handler << clip << animatorData << clipData;
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
const qint64 globalStartTimeNS = 0;
const int loops = 1;
auto animator = createClipAnimator(handler, globalStartTimeNS, loops);
animator->setCurrentLoop(0);
clipData.currentLoop = animator->currentLoop();
const qint64 elapsedTimeNS = toNsecs(clip->duration()+1); // +1 to ensure beyond end
animatorData = evaluationDataForAnimator(animator, nullptr, elapsedTimeNS); // Tested elsewhere
clipData.localTime = localTimeFromElapsedTime(animatorData.currentTime,
animatorData.elapsedTime,
animatorData.playbackRate,
clip->duration(),
animatorData.loopCount,
clipData.currentLoop); // Tested elsewhere
clipData.isFinalFrame = true;
QTest::newRow("clip1.json, elapsedTime = duration + 1")
<< handler << clip << animatorData << clipData;
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
const qint64 globalStartTimeNS = 0;
const int loops = 0; // Infinite loops
auto animator = createClipAnimator(handler, globalStartTimeNS, loops);
animator->setCurrentLoop(0);
clipData.currentLoop = animator->currentLoop();
const qint64 elapsedTimeNS = toNsecs(2.0 * clip->duration());
animatorData = evaluationDataForAnimator(animator, clock, elapsedTimeNS); // Tested elsewhere
clipData.localTime = localTimeFromElapsedTime(animatorData.currentTime,
animatorData.elapsedTime,
animatorData.playbackRate,
clip->duration(),
animatorData.loopCount,
clipData.currentLoop); // Tested elsewhere
clipData.isFinalFrame = false;
QTest::newRow("clip1.json, elapsedTime = 2 * duration, loops = infinite")
<< handler << clip << animatorData << clipData;
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
const qint64 globalStartTimeNS = 0;
const int loops = 2;
auto animator = createClipAnimator(handler, globalStartTimeNS, loops);
animator->setCurrentLoop(0);
clipData.currentLoop = animator->currentLoop();
const qint64 elapsedTimeNS = toNsecs(2.0 * clip->duration() + 1.0); // +1 to ensure beyond end of clip
animatorData = evaluationDataForAnimator(animator, nullptr, elapsedTimeNS); // Tested elsewhere
clipData.localTime = localTimeFromElapsedTime(animatorData.currentTime,
animatorData.elapsedTime,
animatorData.playbackRate,
clip->duration(),
animatorData.loopCount,
clipData.currentLoop); // Tested elsewhere
clipData.isFinalFrame = true;
QTest::newRow("clip1.json, elapsedTime = 2 * duration + 1, loops = 2")
<< handler << clip << animatorData << clipData;
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
const qint64 globalStartTimeNS = 0;
const int loops = 2;
auto animator = createClipAnimator(handler, globalStartTimeNS, loops);
animator->setCurrentLoop(1);
clipData.currentLoop = animator->currentLoop();
const qint64 elapsedTimeNS = toNsecs(clip->duration() + 1.0); // +1 to ensure beyond end of clip
animatorData = evaluationDataForAnimator(animator, nullptr, elapsedTimeNS); // Tested elsewhere
clipData.localTime = localTimeFromElapsedTime(animatorData.currentTime,
animatorData.elapsedTime,
animatorData.playbackRate,
clip->duration(),
animatorData.loopCount,
clipData.currentLoop); // Tested elsewhere
clipData.isFinalFrame = true;
QTest::newRow("clip1.json, elapsedTime = duration + 1, loops = 2, current_loop = 1")
<< handler << clip << animatorData << clipData;
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
const qint64 globalStartTimeNS = clip->duration();
const int loops = 1;
auto animator = createClipAnimator(handler, globalStartTimeNS, loops);
animator->setCurrentLoop(1);
clipData.currentLoop = animator->currentLoop();
const qint64 elapsedTimeNS = toNsecs(clip->duration() * 0.5); // +1 to ensure beyond end of clip
Clock clock;
clock.setPlaybackRate(-1.0);
animatorData = evaluationDataForAnimator(animator, &clock, elapsedTimeNS); // Tested elsewhere
clipData.localTime = localTimeFromElapsedTime(animatorData.currentTime,
animatorData.elapsedTime,
animatorData.playbackRate,
clip->duration(),
animatorData.loopCount,
clipData.currentLoop); // Tested elsewhere
clipData.isFinalFrame = false;
QTest::newRow("clip1.json, elapsedTime = duration / 2, loops = 1, current_loop = 1, playback_rate = -1")
<< handler << clip << animatorData << clipData;
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
const qint64 globalStartTimeNS = clip->duration();
const int loops = 1;
auto animator = createClipAnimator(handler, globalStartTimeNS, loops);
animator->setCurrentLoop(1);
clipData.currentLoop = animator->currentLoop();
const qint64 elapsedTimeNS = toNsecs(clip->duration() + 1); // +1 to ensure beyond end of clip
Clock clock;
clock.setPlaybackRate(-1.0);
animatorData = evaluationDataForAnimator(animator, &clock, elapsedTimeNS); // Tested elsewhere
clipData.localTime = localTimeFromElapsedTime(animatorData.currentTime,
animatorData.elapsedTime,
animatorData.playbackRate,
clip->duration(),
animatorData.loopCount,
clipData.currentLoop); // Tested elsewhere
clipData.isFinalFrame = true;
QTest::newRow("clip1.json, elapsedTime = duration + 1, loops = 1, current_loop = 1, playback_rate = -1")
<< handler << clip << animatorData << clipData;
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
const qint64 globalStartTimeNS = clip->duration();
const int loops = 2;
auto animator = createClipAnimator(handler, globalStartTimeNS, loops);
animator->setCurrentLoop(0);
clipData.currentLoop = animator->currentLoop();
const qint64 elapsedTimeNS = toNsecs(clip->duration() + 1); // +1 to ensure beyond end of clip
Clock clock;
clock.setPlaybackRate(-1.0);
animatorData = evaluationDataForAnimator(animator, &clock, elapsedTimeNS); // Tested elsewhere
clipData.localTime = localTimeFromElapsedTime(animatorData.currentTime,
animatorData.elapsedTime,
animatorData.playbackRate,
clip->duration(),
animatorData.loopCount,
clipData.currentLoop); // Tested elsewhere
clipData.isFinalFrame = true;
QTest::newRow("clip1.json, elapsedTime = duration + 1, loops = 2, current_loop = 0, playback_rate = -1")
<< handler << clip << animatorData << clipData;
}
{
handler = new Handler();
clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json"));
const qint64 globalStartTimeNS = clip->duration();
const int loops = 2;
auto animator = createClipAnimator(handler, globalStartTimeNS, loops);
animator->setCurrentLoop(1);
clipData.currentLoop = animator->currentLoop();
const qint64 elapsedTimeNS = toNsecs(clip->duration() * 2.0 + 1); // +1 to ensure beyond end of clip
Clock clock;
clock.setPlaybackRate(-1.0);
animatorData = evaluationDataForAnimator(animator, &clock, elapsedTimeNS); // Tested elsewhere
clipData.localTime = localTimeFromElapsedTime(animatorData.currentTime,
animatorData.elapsedTime,
animatorData.playbackRate,
clip->duration(),
animatorData.loopCount,
clipData.currentLoop); // Tested elsewhere
clipData.isFinalFrame = true;
QTest::newRow("clip1.json, elapsedTime = duration + 1, loops = 2, current_loop = 1, playback_rate = -1")
<< handler << clip << animatorData << clipData;
}
}
void checkEvaluationDataForClip()
{
// GIVEN
QFETCH(Handler *, handler);
QFETCH(AnimationClip *, clip);
QFETCH(AnimatorEvaluationData, animatorData);
QFETCH(ClipEvaluationData, expectedClipData);
// WHEN
ClipEvaluationData actualClipData = evaluationDataForClip(clip, animatorData);
// THEN
QCOMPARE(actualClipData.currentLoop, expectedClipData.currentLoop);
QVERIFY(fuzzyCompare(actualClipData.localTime, expectedClipData.localTime) == true);
QCOMPARE(actualClipData.isFinalFrame, expectedClipData.isFinalFrame);
// Cleanup
delete handler;
}
void checkEvaluationDataForAnimator_data()
{
QTest::addColumn<Handler *>("handler");
QTest::addColumn<ClipAnimator *>("animator");
QTest::addColumn<qint64>("elapsedTime");
QTest::addColumn<AnimatorEvaluationData>("expectedAnimatorData");
Handler *handler;
ClipAnimator *animator;
qint64 elapsedTimeNS;
AnimatorEvaluationData expectedAnimatorData;
{
handler = new Handler();
const qint64 globalStartTimeNS = 0;
const int loops = 1;
animator = createClipAnimator(handler, globalStartTimeNS, loops);
elapsedTimeNS = 0;
expectedAnimatorData.loopCount = loops;
expectedAnimatorData.playbackRate = 1.0; // hard-wired for now
expectedAnimatorData.elapsedTime = 0.0;
QTest::newRow("globalStartTime = 0, elapsedTime = 0, loops = 1")
<< handler << animator << elapsedTimeNS << expectedAnimatorData;
}
{
handler = new Handler();
const qint64 globalStartTimeNS = 0;
const int loops = 5;
animator = createClipAnimator(handler, globalStartTimeNS, loops);
elapsedTimeNS = 0;
expectedAnimatorData.loopCount = loops;
expectedAnimatorData.playbackRate = 1.0; // hard-wired for now
expectedAnimatorData.elapsedTime = 0.0;
QTest::newRow("globalStartTime = 0, elapsedTime = 0, loops = 5")
<< handler << animator << elapsedTimeNS << expectedAnimatorData;
}
{
handler = new Handler();
const qint64 globalStartTimeNS = 0;
const int loops = 1;
animator = createClipAnimator(handler, globalStartTimeNS, loops);
elapsedTimeNS = 5000000000;
expectedAnimatorData.loopCount = loops;
expectedAnimatorData.playbackRate = 1.0; // hard-wired for now
expectedAnimatorData.elapsedTime = 5.0;
QTest::newRow("globalStartTime = 0, elapsedTime = 5, loops = 1")
<< handler << animator << elapsedTimeNS << expectedAnimatorData;
}
{
handler = new Handler();
const qint64 globalStartTimeNS = 3000000000;
const int loops = 1;
animator = createClipAnimator(handler, globalStartTimeNS, loops);
elapsedTimeNS = 2000000000;
expectedAnimatorData.loopCount = loops;
expectedAnimatorData.playbackRate = 1.0; // hard-wired for now
expectedAnimatorData.elapsedTime = 2.0;
QTest::newRow("globalStartTime = 3, elapsedTime = 2, loops = 1")
<< handler << animator << elapsedTimeNS << expectedAnimatorData;
}
}
void checkEvaluationDataForAnimator()
{
// GIVEN
QFETCH(Handler *, handler);
QFETCH(ClipAnimator *, animator);
QFETCH(qint64, elapsedTime);
QFETCH(AnimatorEvaluationData, expectedAnimatorData);
// WHEN
AnimatorEvaluationData actualAnimatorData = evaluationDataForAnimator(animator, nullptr, elapsedTime);
// THEN
QCOMPARE(actualAnimatorData.loopCount, expectedAnimatorData.loopCount);
QVERIFY(fuzzyCompare(actualAnimatorData.playbackRate, expectedAnimatorData.playbackRate) == true);
QVERIFY(fuzzyCompare(actualAnimatorData.elapsedTime, expectedAnimatorData.elapsedTime) == true);
// Cleanup
delete handler;
}
void checkGatherValueNodesToEvaluate_data()
{
QTest::addColumn<Handler *>("handler");
QTest::addColumn<Qt3DCore::QNodeId>("blendTreeRootId");
QTest::addColumn<QVector<Qt3DCore::QNodeId>>("expectedIds");
{
Handler *handler = new Handler;
const auto lerp = createLerpClipBlend(handler);
const auto value1 = createClipBlendValue(handler);
const auto clip1Id = Qt3DCore::QNodeId::createId();
value1->setClipId(clip1Id);
lerp->setStartClipId(value1->peerId());
const auto value2 = createClipBlendValue(handler);
const auto clip2Id = Qt3DCore::QNodeId::createId();
value2->setClipId(clip2Id);
lerp->setEndClipId(value2->peerId());
QVector<Qt3DCore::QNodeId> expectedIds = { value1->peerId(), value2->peerId() };
QTest::newRow("simple lerp") << handler << lerp->peerId() << expectedIds;
}
{
Handler *handler = new Handler;
const auto value1 = createClipBlendValue(handler);
const auto clip1Id = Qt3DCore::QNodeId::createId();
value1->setClipId(clip1Id);
QVector<Qt3DCore::QNodeId> expectedIds = { value1->peerId() };
QTest::newRow("value only") << handler << value1->peerId() << expectedIds;
}
}
void checkGatherValueNodesToEvaluate()
{
// GIVEN
QFETCH(Handler *, handler);
QFETCH(Qt3DCore::QNodeId, blendTreeRootId);
QFETCH(QVector<Qt3DCore::QNodeId>, expectedIds);
// WHEN
QVector<Qt3DCore::QNodeId> actualIds = gatherValueNodesToEvaluate(handler, blendTreeRootId);
// THEN
QCOMPARE(actualIds.size(), expectedIds.size());
for (int i = 0; i < actualIds.size(); ++i)
QCOMPARE(actualIds[i], expectedIds[i]);
// Cleanup
delete handler;
}
void checkEvaluateBlendTree_data()
{
QTest::addColumn<Handler *>("handler");
QTest::addColumn<BlendedClipAnimator *>("animator");
QTest::addColumn<Qt3DCore::QNodeId>("blendNodeId");
QTest::addColumn<ClipResults>("expectedResults");
{
/*
ValueNode1----
|
MeanBlendNode
|
ValueNode2----
*/
auto handler = new Handler();
const qint64 globalStartTimeNS = 0;
const int loopCount = 1;
auto animator = createBlendedClipAnimator(handler, globalStartTimeNS, loopCount);
// Set up the blend node and dependencies (evaluated clip results of the
// dependent nodes in the animator indexed by their ids).
MeanBlendNode *blendNode = createMeanBlendNode(handler);
// First clip to use in the mean
auto valueNode1 = createClipBlendValue(handler);
ClipResults valueNode1Results = { 0.0f, 0.0f, 0.0f };
valueNode1->setClipResults(animator->peerId(), valueNode1Results);
// Second clip to use in the mean
auto valueNode2 = createClipBlendValue(handler);
ClipResults valueNode2Results = { 1.0f, 1.0f, 1.0f };
valueNode2->setClipResults(animator->peerId(), valueNode2Results);
blendNode->setValueNodeIds(valueNode1->peerId(), valueNode2->peerId());
ClipResults expectedResults = { 0.5f, 0.5f, 0.5f };
QTest::newRow("mean node, 1 channel")
<< handler << animator << blendNode->peerId() << expectedResults;
}
{
/*
ValueNode1----
|
MeanBlendNode
|
ValueNode2----
*/
auto handler = new Handler();
const qint64 globalStartTimeNS = 0;
const int loopCount = 1;
auto animator = createBlendedClipAnimator(handler, globalStartTimeNS, loopCount);
// Set up the blend node and dependencies (evaluated clip results of the
// dependent nodes in the animator indexed by their ids).
MeanBlendNode *blendNode = createMeanBlendNode(handler);
// First clip to use in the mean
auto valueNode1 = createClipBlendValue(handler);
ClipResults valueNode1Results = { 0.0f, 0.0f, 0.0f, 1.0f, 2.0f, 3.0f };
valueNode1->setClipResults(animator->peerId(), valueNode1Results);
// Second clip to use in the mean
auto valueNode2 = createClipBlendValue(handler);
ClipResults valueNode2Results = { 1.0f, 1.0f, 1.0f, 2.0f, 4.0f, 6.0f };
valueNode2->setClipResults(animator->peerId(), valueNode2Results);
blendNode->setValueNodeIds(valueNode1->peerId(), valueNode2->peerId());
ClipResults expectedResults = { 0.5f, 0.5f, 0.5f, 1.5f, 3.0f, 4.5f };
QTest::newRow("mean node, 2 channels")
<< handler << animator << blendNode->peerId() << expectedResults;
}
{
/*
ValueNode1----
|
MeanBlendNode1------
| |
ValueNode2---- |
MeanBlendNode3
ValueNode3---- |
| |
MeanBlendNode2------
|
ValueNode4----
*/
auto handler = new Handler();
const qint64 globalStartTimeNS = 0;
const int loopCount = 1;
auto animator = createBlendedClipAnimator(handler, globalStartTimeNS, loopCount);
// Set up the blend node and dependencies (evaluated clip results of the
// dependent nodes in the animator indexed by their ids).
// MeanBlendNode1
MeanBlendNode *meanNode1 = createMeanBlendNode(handler);
// First clip to use in mean1
auto valueNode1 = createClipBlendValue(handler);
ClipResults valueNode1Results = { 0.0f, 0.0f, 0.0f };
valueNode1->setClipResults(animator->peerId(), valueNode1Results);
// Second clip to use in mean1
auto valueNode2 = createClipBlendValue(handler);
ClipResults valueNode2Results = { 2.0f, 2.0f, 2.0f };
valueNode2->setClipResults(animator->peerId(), valueNode2Results);
meanNode1->setValueNodeIds(valueNode1->peerId(), valueNode2->peerId());
// MeanBlendNode2
MeanBlendNode *meanNode2 = createMeanBlendNode(handler);
// First clip to use in mean1
auto valueNode3 = createClipBlendValue(handler);
ClipResults valueNode3Results = { 10.0f, 10.0f, 10.0f };
valueNode3->setClipResults(animator->peerId(), valueNode3Results);
// Second clip to use in mean1
auto valueNode4 = createClipBlendValue(handler);
ClipResults valueNode4Results = { 20.0f, 20.0f, 20.0f };
valueNode4->setClipResults(animator->peerId(), valueNode4Results);
meanNode2->setValueNodeIds(valueNode3->peerId(), valueNode4->peerId());
// MeanBlendNode3
MeanBlendNode *meanNode3 = createMeanBlendNode(handler);
meanNode3->setValueNodeIds(meanNode1->peerId(), meanNode2->peerId());
// Mean1 = 1
// Mean2 = 15
// Mean3 = (1 + 15 ) / 2 = 8
ClipResults expectedResults = { 8.0f, 8.0f, 8.0f };
QTest::newRow("3 mean nodes, 1 channel")
<< handler << animator << meanNode3->peerId() << expectedResults;
}
{
/*
ValueNode1----
|
MeanBlendNode1------
| |
ValueNode2---- |
MeanBlendNode3---
ValueNode3---- | |
| | |
MeanBlendNode2------ AdditiveBlendNode1
| |
ValueNode4---- |
ValueNode5-------
*/
auto handler = new Handler();
const qint64 globalStartTimeNS = 0;
const int loopCount = 1;
auto animator = createBlendedClipAnimator(handler, globalStartTimeNS, loopCount);
// Set up the blend node and dependencies (evaluated clip results of the
// dependent nodes in the animator indexed by their ids).
// MeanBlendNode1
MeanBlendNode *meanNode1 = createMeanBlendNode(handler);
// First clip to use in mean1
auto valueNode1 = createClipBlendValue(handler);
ClipResults valueNode1Results = { 0.0f, 0.0f, 0.0f };
valueNode1->setClipResults(animator->peerId(), valueNode1Results);
// Second clip to use in mean1
auto valueNode2 = createClipBlendValue(handler);
ClipResults valueNode2Results = { 2.0f, 2.0f, 2.0f };
valueNode2->setClipResults(animator->peerId(), valueNode2Results);
meanNode1->setValueNodeIds(valueNode1->peerId(), valueNode2->peerId());
// MeanBlendNode2
MeanBlendNode *meanNode2 = createMeanBlendNode(handler);
// First clip to use in mean2
auto valueNode3 = createClipBlendValue(handler);
ClipResults valueNode3Results = { 10.0f, 10.0f, 10.0f };
valueNode3->setClipResults(animator->peerId(), valueNode3Results);
// Second clip to use in mean2
auto valueNode4 = createClipBlendValue(handler);
ClipResults valueNode4Results = { 20.0f, 20.0f, 20.0f };
valueNode4->setClipResults(animator->peerId(), valueNode4Results);
meanNode2->setValueNodeIds(valueNode3->peerId(), valueNode4->peerId());
// MeanBlendNode3
MeanBlendNode *meanNode3 = createMeanBlendNode(handler);
meanNode3->setValueNodeIds(meanNode1->peerId(), meanNode2->peerId());
// AdditiveBlendNode1
AdditiveClipBlend *additiveBlendNode1 = createAdditiveClipBlend(handler);
auto valueNode5 = createClipBlendValue(handler);
ClipResults valueNode5Results = { 1.0f, 2.0f, 3.0f };
valueNode5->setClipResults(animator->peerId(), valueNode5Results);
additiveBlendNode1->setBaseClipId(meanNode3->peerId());
additiveBlendNode1->setAdditiveClipId(valueNode5->peerId());
additiveBlendNode1->setAdditiveFactor(0.5);
// Mean1 = 1
// Mean2 = 15
// Mean3 = (1 + 15 ) / 2 = 8
// Additive1 = 8 + 0.5 * (1, 2, 3) = (8.5, 9, 9.5)
ClipResults expectedResults = { 8.5f, 9.0f, 9.5f };
QTest::newRow("3 mean nodes + additive, 1 channel")
<< handler << animator << additiveBlendNode1->peerId() << expectedResults;
}
}
void checkEvaluateBlendTree()
{
// GIVEN
QFETCH(Handler *, handler);
QFETCH(BlendedClipAnimator *, animator);
QFETCH(Qt3DCore::QNodeId, blendNodeId);
QFETCH(ClipResults, expectedResults);
// WHEN
const ClipResults actualResults = evaluateBlendTree(handler, animator, blendNodeId);
// THEN
QCOMPARE(actualResults.size(), expectedResults.size());
for (int i = 0; i < actualResults.size(); ++i)
QCOMPARE(actualResults[i], expectedResults[i]);
// Cleanup
delete handler;
}
void checkFormatClipResults_data()
{
QTest::addColumn<ClipResults>("rawClipResults");
QTest::addColumn<ComponentIndices>("format");
QTest::addColumn<ClipResults>("expectedResults");
{
ClipResults rawClipResults = { 1.0f, 2.0f, 3.0f };
ComponentIndices format = { 0, 1, 2 };
ClipResults expectedResults = { 1.0f, 2.0f, 3.0f };
QTest::newRow("identity")
<< rawClipResults << format << expectedResults;
}
{
ClipResults rawClipResults = { 1.0f, 2.0f };
ComponentIndices format = { 1, 0 };
ClipResults expectedResults = { 2.0f, 1.0f };
QTest::newRow("swap")
<< rawClipResults << format << expectedResults;
}
{
ClipResults rawClipResults = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
ComponentIndices format = { 0, 2, 1, 3, 4 };
ClipResults expectedResults = { 1.0f, 3.0f, 2.0f, 4.0f, 5.0f };
QTest::newRow("swap subset")
<< rawClipResults << format << expectedResults;
}
{
ClipResults rawClipResults = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
ComponentIndices format = { 4, 3, 2, 1, 0 };
ClipResults expectedResults = { 5.0f, 4.0f, 3.0f, 2.0f, 1.0f };
QTest::newRow("reverse")
<< rawClipResults << format << expectedResults;
}
{
ClipResults rawClipResults = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };
ComponentIndices format = { 0, 1, -1, 3, 4 };
ClipResults expectedResults = { 1.0f, 2.0f, 0.0f, 4.0f, 5.0f };
QTest::newRow("include missing")
<< rawClipResults << format << expectedResults;
}
}
void checkFormatClipResults()
{
// GIVEN
QFETCH(ClipResults, rawClipResults);
QFETCH(ComponentIndices, format);
QFETCH(ClipResults, expectedResults);
// WHEN
const ClipResults actualResults = formatClipResults(rawClipResults, format);
// THEN
QCOMPARE(actualResults.size(), expectedResults.size());
for (int i = 0; i < actualResults.size(); ++i)
QCOMPARE(actualResults[i], expectedResults[i]);
}
void checkBuildRequiredChannelsAndTypes_data()
{
QTest::addColumn<Handler *>("handler");
QTest::addColumn<ChannelMapper *>("mapper");
QTest::addColumn<QVector<ChannelNameAndType>>("expectedResults");
{
auto handler = new Handler();
auto channelMapping = createChannelMapping(handler,
QLatin1String("Location"),
Qt3DCore::QNodeId::createId(),
"translation",
static_cast<int>(QVariant::Vector3D),
3);
QVector<ChannelMapping *> channelMappings;
channelMappings.push_back(channelMapping);
auto channelMapper = createChannelMapper(handler,
QVector<Qt3DCore::QNodeId>() << channelMapping->peerId());
QVector<ChannelNameAndType> expectedResults;
expectedResults.push_back({ QLatin1String("Location"),
static_cast<int>(QVariant::Vector3D),
3,
channelMapping->peerId() });
QTest::addRow("Location, vec3") << handler << channelMapper << expectedResults;
}
{
auto handler = new Handler();
auto channelMapping1 = createChannelMapping(handler,
QLatin1String("Location"),
Qt3DCore::QNodeId::createId(),
"translation",
static_cast<int>(QVariant::Vector3D),
3);
auto channelMapping2 = createChannelMapping(handler,
QLatin1String("Rotation"),
Qt3DCore::QNodeId::createId(),
"rotation",
static_cast<int>(QVariant::Quaternion),
4);
QVector<ChannelMapping *> channelMappings;
channelMappings.push_back(channelMapping1);
channelMappings.push_back(channelMapping2);
QVector<Qt3DCore::QNodeId> channelMappingIds
= (QVector<Qt3DCore::QNodeId>()
<< channelMapping1->peerId()
<< channelMapping2->peerId());
auto channelMapper = createChannelMapper(handler, channelMappingIds);
QVector<ChannelNameAndType> expectedResults;
expectedResults.push_back({ QLatin1String("Location"),
static_cast<int>(QVariant::Vector3D),
3,
channelMapping1->peerId() });
expectedResults.push_back({ QLatin1String("Rotation"),
static_cast<int>(QVariant::Quaternion),
4,
channelMapping2->peerId() });
QTest::addRow("Multiple unique channels") << handler << channelMapper << expectedResults;
}
{
auto handler = new Handler();
auto channelMapping1 = createChannelMapping(handler,
QLatin1String("Location"),
Qt3DCore::QNodeId::createId(),
"translation",
static_cast<int>(QVariant::Vector3D),
3);
auto channelMapping2 = createChannelMapping(handler,
QLatin1String("Rotation"),
Qt3DCore::QNodeId::createId(),
"rotation",
static_cast<int>(QVariant::Quaternion),
4);
auto channelMapping3 = createChannelMapping(handler,
QLatin1String("Location"),
Qt3DCore::QNodeId::createId(),
"translation",
static_cast<int>(QVariant::Vector3D),
3);
auto channelMapping4 = createChannelMapping(handler,
QLatin1String("Location"),
Qt3DCore::QNodeId::createId(),
"translation",
static_cast<int>(QVariant::Vector3D),
3);
QVector<ChannelMapping *> channelMappings;
channelMappings.push_back(channelMapping1);
channelMappings.push_back(channelMapping2);
channelMappings.push_back(channelMapping3);
channelMappings.push_back(channelMapping4);
QVector<Qt3DCore::QNodeId> channelMappingIds
= (QVector<Qt3DCore::QNodeId>()
<< channelMapping1->peerId()
<< channelMapping2->peerId()
<< channelMapping3->peerId()
<< channelMapping4->peerId());
auto channelMapper = createChannelMapper(handler, channelMappingIds);
QVector<ChannelNameAndType> expectedResults;
expectedResults.push_back({ QLatin1String("Location"),
static_cast<int>(QVariant::Vector3D),
3,
channelMapping1->peerId() });
expectedResults.push_back({ QLatin1String("Rotation"),
static_cast<int>(QVariant::Quaternion),
4,
channelMapping2->peerId() });
expectedResults.push_back({ QLatin1String("Location"),
static_cast<int>(QVariant::Vector3D),
3,
channelMapping3->peerId() });
expectedResults.push_back({ QLatin1String("Location"),
static_cast<int>(QVariant::Vector3D),
3,
channelMapping4->peerId() });
QTest::addRow("Multiple channels with repeats") << handler << channelMapper << expectedResults;
}
{
auto handler = new Handler();
const int jointCount = 10;
auto skeleton = createSkeleton(handler, jointCount);
auto channelMapping = createChannelMapping(handler, skeleton->peerId());
QVector<ChannelMapping *> channelMappings;
channelMappings.push_back(channelMapping);
auto channelMapper = createChannelMapper(handler,
QVector<Qt3DCore::QNodeId>() << channelMapping->peerId());
QVector<ChannelNameAndType> expectedResults;
for (int i = 0; i < jointCount; ++i) {
ChannelNameAndType locationDescription = { QLatin1String("Location"),
static_cast<int>(QVariant::Vector3D),
3,
channelMapping->peerId() };
locationDescription.jointIndex = i;
locationDescription.jointTransformComponent = Translation;
expectedResults.push_back(locationDescription);
ChannelNameAndType rotationDescription = { QLatin1String("Rotation"),
static_cast<int>(QVariant::Quaternion),
4,
channelMapping->peerId() };
rotationDescription.jointIndex = i;
rotationDescription.jointTransformComponent = Rotation;
expectedResults.push_back(rotationDescription);
ChannelNameAndType scaleDescription = { QLatin1String("Scale"),
static_cast<int>(QVariant::Vector3D),
3,
channelMapping->peerId() };
scaleDescription.jointIndex = i;
scaleDescription.jointTransformComponent = Scale;
expectedResults.push_back(scaleDescription);
}
QTest::addRow("Skeleton, 10 joints") << handler << channelMapper << expectedResults;
}
}
void checkBuildRequiredChannelsAndTypes()
{
// GIVEN
QFETCH(Handler *, handler);
QFETCH(ChannelMapper *, mapper);
QFETCH(QVector<ChannelNameAndType>, expectedResults);
// WHEN
const QVector<ChannelNameAndType> actualResults = buildRequiredChannelsAndTypes(handler, mapper);
// THEN
QCOMPARE(actualResults.size(), expectedResults.size());
for (int i = 0; i < actualResults.size(); ++i)
QCOMPARE(actualResults[i], expectedResults[i]);
// Cleanup
delete handler;
}
void checkAssignChannelComponentIndices_data()
{
QTest::addColumn<QVector<ChannelNameAndType>>("allChannels");
QTest::addColumn<QVector<ComponentIndices>>("expectedResults");
{
QVector<ChannelNameAndType> allChannels;
allChannels.push_back({ QLatin1String("Location"), static_cast<int>(QVariant::Vector3D), 3 });
QVector<ComponentIndices> expectedResults;
expectedResults.push_back({ 0, 1, 2 });
QTest::newRow("vec3 location") << allChannels << expectedResults;
}
{
QVector<ChannelNameAndType> allChannels;
allChannels.push_back({ QLatin1String("Location"), static_cast<int>(QVariant::Vector3D), 3 });
allChannels.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion), 4 });
QVector<ComponentIndices> expectedResults;
expectedResults.push_back({ 0, 1, 2 });
expectedResults.push_back({ 3, 4, 5, 6 });
QTest::newRow("vec3 location, quaterion rotation") << allChannels << expectedResults;
}
{
QVector<ChannelNameAndType> allChannels;
allChannels.push_back({ QLatin1String("Location"), static_cast<int>(QVariant::Vector3D), 3 });
allChannels.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion), 4 });
allChannels.push_back({ QLatin1String("BaseColor"), static_cast<int>(QVariant::Vector3D), 3 });
allChannels.push_back({ QLatin1String("Metalness"), static_cast<int>(QVariant::Double), 1 });
allChannels.push_back({ QLatin1String("Roughness"), static_cast<int>(QVariant::Double), 1 });
allChannels.push_back({ QLatin1String("MorphWeights"), static_cast<int>(QVariant::List), 6 });
QVector<ComponentIndices> expectedResults;
expectedResults.push_back({ 0, 1, 2 });
expectedResults.push_back({ 3, 4, 5, 6 });
expectedResults.push_back({ 7, 8, 9 });
expectedResults.push_back({ 10 });
expectedResults.push_back({ 11 });
expectedResults.push_back({ 12, 13, 14, 15, 16, 17 });
QTest::newRow("vec3 location, quaterion rotation, pbr metal-rough morphweights") << allChannels << expectedResults;
}
{
QVector<ChannelNameAndType> allChannels;
const int jointCount = 4;
for (int i = 0; i < jointCount; ++i) {
ChannelNameAndType locationDescription = { QLatin1String("Location"), static_cast<int>(QVariant::Vector3D), 3 };
locationDescription.jointIndex = i;
allChannels.push_back(locationDescription);
ChannelNameAndType rotationDescription = { QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion), 4 };
rotationDescription.jointIndex = i;
allChannels.push_back(rotationDescription);
ChannelNameAndType scaleDescription = { QLatin1String("Scale"), static_cast<int>(QVariant::Vector3D), 3 };
scaleDescription.jointIndex = i;
allChannels.push_back(scaleDescription);
}
QVector<ComponentIndices> expectedResults;
expectedResults.push_back({ 0, 1, 2 });
expectedResults.push_back({ 3, 4, 5, 6 });
expectedResults.push_back({ 7, 8, 9 });
expectedResults.push_back({ 10, 11, 12 });
expectedResults.push_back({ 13, 14, 15, 16 });
expectedResults.push_back({ 17, 18, 19 });
expectedResults.push_back({ 20, 21, 22 });
expectedResults.push_back({ 23, 24, 25, 26 });
expectedResults.push_back({ 27, 28, 29 });
expectedResults.push_back({ 30, 31, 32 });
expectedResults.push_back({ 33, 34, 35, 36 });
expectedResults.push_back({ 37, 38, 39 });
QTest::newRow("skeleton, 4 joints") << allChannels << expectedResults;
}
}
void checkAssignChannelComponentIndices()
{
// GIVEN
QFETCH(QVector<ChannelNameAndType>, allChannels);
QFETCH(QVector<ComponentIndices>, expectedResults);
// WHEN
const QVector<ComponentIndices> actualResults = assignChannelComponentIndices(allChannels);
// THEN
QCOMPARE(actualResults.size(), expectedResults.size());
for (int i = 0; i < actualResults.size(); ++i) {
const ComponentIndices &actualResult = actualResults[i];
const ComponentIndices &expectedResult = expectedResults[i];
for (int j = 0; j < actualResult.size(); ++j)
QCOMPARE(actualResult[j], expectedResult[j]);
}
}
void checkGenerateClipFormatIndices_data()
{
QTest::addColumn<QVector<ChannelNameAndType>>("targetChannels");
QTest::addColumn<QVector<ComponentIndices>>("targetIndices");
QTest::addColumn<AnimationClip *>("clip");
QTest::addColumn<ClipFormat>("expectedResults");
{
QVector<ChannelNameAndType> targetChannels;
targetChannels.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion), 4 });
targetChannels.push_back({ QLatin1String("Location"), static_cast<int>(QVariant::Vector3D), 3 });
targetChannels.push_back({ QLatin1String("Base Color"), static_cast<int>(QVariant::Vector3D), 3 });
targetChannels.push_back({ QLatin1String("Metalness"), static_cast<int>(QVariant::Double), 1 });
targetChannels.push_back({ QLatin1String("Roughness"), static_cast<int>(QVariant::Double), 1 });
QVector<ComponentIndices> targetIndices;
targetIndices.push_back({ 0, 1, 2, 3 });
targetIndices.push_back({ 4, 5, 6 });
targetIndices.push_back({ 7, 8, 9 });
targetIndices.push_back({ 10 });
targetIndices.push_back({ 11 });
auto *clip = new AnimationClip();
clip->setDataType(AnimationClip::File);
clip->setSource(QUrl("qrc:/clip3.json"));
clip->loadAnimation();
ClipFormat expectedResults;
expectedResults.sourceClipIndices = { 0, 1, 3, 2, // Rotation (y/z swapped in clip3.json)
4, 6, 5, // Location (y/z swapped in clip3.json)
7, 8, 9, // Base Color
10, // Metalness
11 }; // Roughness
expectedResults.sourceClipMask = { QBitArray(4, true),
QBitArray(3, true),
QBitArray(3, true),
QBitArray(1, true),
QBitArray(1, true) };
expectedResults.namesAndTypes = targetChannels;
expectedResults.formattedComponentIndices = targetIndices;
QTest::newRow("rotation, location, pbr metal-rough")
<< targetChannels << targetIndices << clip << expectedResults;
}
{
QVector<ChannelNameAndType> targetChannels;
targetChannels.push_back({ QLatin1String("Location"), static_cast<int>(QVariant::Vector3D), 3 });
targetChannels.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion), 4 });
targetChannels.push_back({ QLatin1String("Base Color"), static_cast<int>(QVariant::Vector3D), 3 });
targetChannels.push_back({ QLatin1String("Metalness"), static_cast<int>(QVariant::Double), 1 });
targetChannels.push_back({ QLatin1String("Roughness"), static_cast<int>(QVariant::Double), 1 });
QVector<ComponentIndices> targetIndices;
targetIndices.push_back({ 0, 1, 2 });
targetIndices.push_back({ 3, 4, 5, 6 });
targetIndices.push_back({ 7, 8, 9 });
targetIndices.push_back({ 10 });
targetIndices.push_back({ 11 });
auto *clip = new AnimationClip();
clip->setDataType(AnimationClip::File);
clip->setSource(QUrl("qrc:/clip3.json"));
clip->loadAnimation();
ClipFormat expectedResults;
expectedResults.sourceClipIndices = { 4, 6, 5, // Location (y/z swapped in clip3.json)
0, 1, 3, 2, // Rotation (y/z swapped in clip3.json)
7, 8, 9, // Base Color
10, // Metalness
11 }; // Roughness
expectedResults.sourceClipMask = { QBitArray(3, true),
QBitArray(4, true),
QBitArray(3, true),
QBitArray(1, true),
QBitArray(1, true) };
expectedResults.namesAndTypes = targetChannels;
expectedResults.formattedComponentIndices = targetIndices;
QTest::newRow("location, rotation, pbr metal-rough")
<< targetChannels << targetIndices << clip << expectedResults;
}
{
QVector<ChannelNameAndType> targetChannels;
targetChannels.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion), 4 });
targetChannels.push_back({ QLatin1String("Location"), static_cast<int>(QVariant::Vector3D), 3 });
targetChannels.push_back({ QLatin1String("Albedo"), static_cast<int>(QVariant::Vector3D), 3 });
targetChannels.push_back({ QLatin1String("Metalness"), static_cast<int>(QVariant::Double), 1 });
targetChannels.push_back({ QLatin1String("Roughness"), static_cast<int>(QVariant::Double), 1 });
QVector<ComponentIndices> targetIndices;
targetIndices.push_back({ 0, 1, 2, 3 });
targetIndices.push_back({ 4, 5, 6 });
targetIndices.push_back({ 7, 8, 9 });
targetIndices.push_back({ 10 });
targetIndices.push_back({ 11 });
auto *clip = new AnimationClip();
clip->setDataType(AnimationClip::File);
clip->setSource(QUrl("qrc:/clip3.json"));
clip->loadAnimation();
ClipFormat expectedResults;
expectedResults.sourceClipIndices = { 0, 1, 3, 2, // Rotation (y/z swapped in clip3.json)
4, 6, 5, // Location (y/z swapped in clip3.json)
-1, -1, -1, // Albedo (missing from clip)
10, // Metalness
11 }; // Roughness
expectedResults.sourceClipMask = { QBitArray(4, true),
QBitArray(3, true),
QBitArray(3, false),
QBitArray(1, true),
QBitArray(1, true) };
expectedResults.namesAndTypes = targetChannels;
expectedResults.formattedComponentIndices = targetIndices;
QTest::newRow("rotation, location, albedo (missing), metal-rough")
<< targetChannels << targetIndices << clip << expectedResults;
}
{
QVector<ChannelNameAndType> targetChannels;
targetChannels.push_back({ QLatin1String("Location"), static_cast<int>(QVariant::Vector3D), 3 });
targetChannels.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion), 4 });
targetChannels.push_back({ QLatin1String("Albedo"), static_cast<int>(QVariant::Vector3D), 3 });
targetChannels.push_back({ QLatin1String("Metalness"), static_cast<int>(QVariant::Double), 1 });
targetChannels.push_back({ QLatin1String("Roughness"), static_cast<int>(QVariant::Double), 1 });
QVector<ComponentIndices> targetIndices;
targetIndices.push_back({ 0, 1, 2 });
targetIndices.push_back({ 3, 4, 5, 6 });
targetIndices.push_back({ 7, 8, 9 });
targetIndices.push_back({ 10 });
targetIndices.push_back({ 11 });
auto *clip = new AnimationClip();
clip->setDataType(AnimationClip::File);
clip->setSource(QUrl("qrc:/clip3.json"));
clip->loadAnimation();
ClipFormat expectedResults;
expectedResults.sourceClipIndices = { 4, 6, 5, // Location (y/z swapped in clip3.json)
0, 1, 3, 2, // Rotation (y/z swapped in clip3.json)
-1, -1, -1, // Albedo (missing from clip)
10, // Metalness
11 }; // Roughness
expectedResults.sourceClipMask = { QBitArray(3, true),
QBitArray(4, true),
QBitArray(3, false),
QBitArray(1, true),
QBitArray(1, true) };
expectedResults.namesAndTypes = targetChannels;
expectedResults.formattedComponentIndices = targetIndices;
QTest::newRow("location, rotation, albedo (missing), metal-rough")
<< targetChannels << targetIndices << clip << expectedResults;
}
{
QVector<ChannelNameAndType> targetChannels;
const int jointCount = 4;
for (int i = 0; i < jointCount; ++i) {
ChannelNameAndType locationDescription = { QLatin1String("Location"), static_cast<int>(QVariant::Vector3D), 3 };
locationDescription.jointIndex = i;
targetChannels.push_back(locationDescription);
ChannelNameAndType rotationDescription = { QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion), 4 };
rotationDescription.jointIndex = i;
targetChannels.push_back(rotationDescription);
ChannelNameAndType scaleDescription = { QLatin1String("Scale"), static_cast<int>(QVariant::Vector3D), 3 };
scaleDescription.jointIndex = i;
targetChannels.push_back(scaleDescription);
}
QVector<ComponentIndices> targetIndices;
targetIndices.push_back({ 0, 1, 2 });
targetIndices.push_back({ 3, 4, 5, 6 });
targetIndices.push_back({ 7, 8, 9 });
targetIndices.push_back({ 10, 11, 12 });
targetIndices.push_back({ 13, 14, 15, 16 });
targetIndices.push_back({ 17, 18, 19 });
targetIndices.push_back({ 20, 21, 22 });
targetIndices.push_back({ 23, 24, 25, 26 });
targetIndices.push_back({ 27, 28, 29 });
targetIndices.push_back({ 30, 31, 32 });
targetIndices.push_back({ 33, 34, 35, 36 });
targetIndices.push_back({ 37, 38, 39 });
auto *clip = new AnimationClip();
clip->setDataType(AnimationClip::File);
clip->setSource(QUrl("qrc:/clip5.json"));
clip->loadAnimation();
ClipFormat expectedResults;
expectedResults.sourceClipIndices = { 4, 6, 5, // Location, joint 0 (y/z swapped in clip5.json)
0, 1, 3, 2, // Rotation, joint 0 (y/z swapped in clip5.json)
7, 8, 9, // Scale, joint 0
14, 16, 15, // Location, joint 1 (y/z swapped in clip5.json)
10, 11, 13, 12, // Rotation, joint 1 (y/z swapped in clip5.json)
17, 18, 19, // Scale, joint 1
24, 26, 25, // Location, joint 2 (y/z swapped in clip5.json)
20, 21, 23, 22, // Rotation, joint 2 (y/z swapped in clip5.json)
27, 28, 29, // Scale, joint 2
34, 36, 35, // Location, joint 3 (y/z swapped in clip5.json)
30, 31, 33, 32, // Rotation, joint 3 (y/z swapped in clip5.json)
37, 38, 39 }; // Scale, joint 3
expectedResults.sourceClipMask = { QBitArray(3, true),
QBitArray(4, true),
QBitArray(3, true),
QBitArray(3, true),
QBitArray(4, true),
QBitArray(3, true),
QBitArray(3, true),
QBitArray(4, true),
QBitArray(3, true),
QBitArray(3, true),
QBitArray(4, true),
QBitArray(3, true) };
expectedResults.namesAndTypes = targetChannels;
expectedResults.formattedComponentIndices = targetIndices;
QTest::newRow("skeleton (SQT), 4 joints")
<< targetChannels << targetIndices << clip << expectedResults;
}
}
void checkGenerateClipFormatIndices()
{
// GIVEN
QFETCH(QVector<ChannelNameAndType>, targetChannels);
QFETCH(QVector<ComponentIndices>, targetIndices);
QFETCH(AnimationClip *, clip);
QFETCH(ClipFormat, expectedResults);
// WHEN
const ClipFormat actualResults = generateClipFormatIndices(targetChannels,
targetIndices,
clip);
// THEN
QCOMPARE(actualResults.sourceClipIndices.size(), expectedResults.sourceClipIndices.size());
for (int i = 0; i < actualResults.sourceClipIndices.size(); ++i)
QCOMPARE(actualResults.sourceClipIndices[i], expectedResults.sourceClipIndices[i]);
QCOMPARE(actualResults.sourceClipMask.size(), expectedResults.sourceClipMask.size());
for (int i = 0; i < actualResults.sourceClipMask.size(); ++i)
QCOMPARE(actualResults.sourceClipMask[i], expectedResults.sourceClipMask[i]);
QCOMPARE(actualResults.formattedComponentIndices.size(), expectedResults.formattedComponentIndices.size());
for (int i = 0; i < actualResults.formattedComponentIndices.size(); ++i)
QCOMPARE(actualResults.formattedComponentIndices[i], expectedResults.formattedComponentIndices[i]);
QCOMPARE(actualResults.namesAndTypes.size(), expectedResults.namesAndTypes.size());
for (int i = 0; i < actualResults.namesAndTypes.size(); ++i)
QCOMPARE(actualResults.namesAndTypes[i], expectedResults.namesAndTypes[i]);
// Cleanup
delete clip;
}
void checkDefaultValueForChannel_data()
{
QTest::addColumn<Handler *>("handler");
QTest::addColumn<ChannelNameAndType>("channelDescription");
QTest::addColumn<QVector<float>>("expectedResults");
{
auto handler = new Handler();
auto channelMapping = createChannelMapping(handler,
QLatin1String("Location"),
Qt3DCore::QNodeId::createId(),
"translation",
static_cast<int>(QVariant::Vector3D),
3);
ChannelNameAndType channelDescription;
channelDescription.mappingId = channelMapping->peerId();
channelDescription.type = static_cast<int>(QVariant::Vector3D);
channelDescription.name = QLatin1String("translation");
const QVector<float> expectedResults = { 0.0f, 0.0f, 0.0f };
QTest::newRow("translation") << handler << channelDescription << expectedResults;
}
{
auto handler = new Handler();
auto channelMapping = createChannelMapping(handler,
QLatin1String("Rotation"),
Qt3DCore::QNodeId::createId(),
"rotation",
static_cast<int>(QVariant::Quaternion),
4);
ChannelNameAndType channelDescription;
channelDescription.mappingId = channelMapping->peerId();
channelDescription.type = static_cast<int>(QVariant::Quaternion);
channelDescription.name = QLatin1String("rotation");
const QVector<float> expectedResults = { 1.0f, 0.0f, 0.0f, 0.0f };
QTest::newRow("rotation") << handler << channelDescription << expectedResults;
}
{
auto handler = new Handler();
auto channelMapping = createChannelMapping(handler,
QLatin1String("Scale"),
Qt3DCore::QNodeId::createId(),
"scale",
static_cast<int>(QVariant::Vector3D),
3);
ChannelNameAndType channelDescription;
channelDescription.mappingId = channelMapping->peerId();
channelDescription.type = static_cast<int>(QVariant::Vector3D);
channelDescription.name = QLatin1String("scale");
const QVector<float> expectedResults = { 1.0f, 1.0f, 1.0f };
QTest::newRow("scale") << handler << channelDescription << expectedResults;
}
// Test skeleton cases
{
auto handler = new Handler();
auto skeleton = createSkeleton(handler, 2);
skeleton->setJointScale(0, QVector3D(2.0f, 3.0f, 4.0f));
auto channelMapping = createChannelMapping(handler, skeleton->peerId());
ChannelNameAndType channelDescription;
channelDescription.mappingId = channelMapping->peerId();
channelDescription.type = static_cast<int>(QVariant::Vector3D);
channelDescription.jointIndex = 0;
channelDescription.jointTransformComponent = Scale;
const QVector<float> expectedResults = { 2.0f, 3.0f, 4.0f };
QTest::newRow("joint 0 scale") << handler << channelDescription << expectedResults;
}
{
auto handler = new Handler();
auto skeleton = createSkeleton(handler, 2);
skeleton->setJointRotation(0, QQuaternion(1.0f, 0.0f, 0.0f, 0.0f));
auto channelMapping = createChannelMapping(handler, skeleton->peerId());
ChannelNameAndType channelDescription;
channelDescription.mappingId = channelMapping->peerId();
channelDescription.type = static_cast<int>(QVariant::Vector3D);
channelDescription.jointIndex = 0;
channelDescription.jointTransformComponent = Rotation;
const QVector<float> expectedResults = { 1.0f, 0.0f, 0.0f, 0.0f };
QTest::newRow("joint 0 rotation") << handler << channelDescription << expectedResults;
}
{
auto handler = new Handler();
auto skeleton = createSkeleton(handler, 2);
skeleton->setJointTranslation(0, QVector3D(2.0f, 3.0f, 4.0f));
auto channelMapping = createChannelMapping(handler, skeleton->peerId());
ChannelNameAndType channelDescription;
channelDescription.mappingId = channelMapping->peerId();
channelDescription.type = static_cast<int>(QVariant::Vector3D);
channelDescription.jointIndex = 0;
channelDescription.jointTransformComponent = Translation;
const QVector<float> expectedResults = { 2.0f, 3.0f, 4.0f };
QTest::newRow("joint 0 translation") << handler << channelDescription << expectedResults;
}
{
auto handler = new Handler();
auto skeleton = createSkeleton(handler, 2);
skeleton->setJointScale(1, QVector3D(20.0f, 30.0f, 40.0f));
auto channelMapping = createChannelMapping(handler, skeleton->peerId());
ChannelNameAndType channelDescription;
channelDescription.mappingId = channelMapping->peerId();
channelDescription.type = static_cast<int>(QVariant::Vector3D);
channelDescription.jointIndex = 1;
channelDescription.jointTransformComponent = Scale;
const QVector<float> expectedResults = { 20.0f, 30.0f, 40.0f };
QTest::newRow("joint 1 scale") << handler << channelDescription << expectedResults;
}
{
auto handler = new Handler();
auto skeleton = createSkeleton(handler, 2);
skeleton->setJointRotation(1, QQuaternion(1.0f, 0.0f, 0.0f, 0.0f));
auto channelMapping = createChannelMapping(handler, skeleton->peerId());
ChannelNameAndType channelDescription;
channelDescription.mappingId = channelMapping->peerId();
channelDescription.type = static_cast<int>(QVariant::Vector3D);
channelDescription.jointIndex = 1;
channelDescription.jointTransformComponent = Rotation;
const QVector<float> expectedResults = { 1.0f, 0.0f, 0.0f, 0.0f };
QTest::newRow("joint 1 rotation") << handler << channelDescription << expectedResults;
}
{
auto handler = new Handler();
auto skeleton = createSkeleton(handler, 2);
skeleton->setJointTranslation(1, QVector3D(4.0f, 5.0f, 6.0f));
auto channelMapping = createChannelMapping(handler, skeleton->peerId());
ChannelNameAndType channelDescription;
channelDescription.mappingId = channelMapping->peerId();
channelDescription.type = static_cast<int>(QVariant::Vector3D);
channelDescription.jointIndex = 1;
channelDescription.jointTransformComponent = Translation;
const QVector<float> expectedResults = { 4.0f, 5.0f, 6.0f };
QTest::newRow("joint 1 translation") << handler << channelDescription << expectedResults;
}
}
void checkDefaultValueForChannel()
{
// GIVEN
QFETCH(Handler *, handler);
QFETCH(ChannelNameAndType, channelDescription);
QFETCH(QVector<float>, expectedResults);
// WHEN
auto actualResults = defaultValueForChannel(handler, channelDescription);
// THEN
QCOMPARE(actualResults.size(), expectedResults.size());
for (int i = 0; i < actualResults.size(); ++i) {
QCOMPARE(actualResults[i], expectedResults[i]);
}
}
};
QTEST_MAIN(tst_AnimationUtils)
#include "tst_animationutils.moc"