diff options
Diffstat (limited to 'tests/auto/animation/animationutils/tst_animationutils.cpp')
-rw-r--r-- | tests/auto/animation/animationutils/tst_animationutils.cpp | 2501 |
1 files changed, 2501 insertions, 0 deletions
diff --git a/tests/auto/animation/animationutils/tst_animationutils.cpp b/tests/auto/animation/animationutils/tst_animationutils.cpp new file mode 100644 index 000000000..ac4fb6fa0 --- /dev/null +++ b/tests/auto/animation/animationutils/tst_animationutils.cpp @@ -0,0 +1,2501 @@ +/**************************************************************************** +** +** 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/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 <Qt3DCore/qpropertyupdatedchange.h> +#include <QtGui/qvector2d.h> +#include <QtGui/qvector3d.h> +#include <QtGui/qvector4d.h> +#include <QtGui/qquaternion.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(ChannelMapper *) +Q_DECLARE_METATYPE(AnimationClip *) +Q_DECLARE_METATYPE(QVector<MappingData>) +Q_DECLARE_METATYPE(QVector<Qt3DCore::QPropertyUpdatedChangePtr>) +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>) + +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 Q_DECL_OVERRIDE + { + return currentDependencyIds(); + } + + QVector<Qt3DCore::QNodeId> currentDependencyIds() const Q_DECL_FINAL + { + return QVector<Qt3DCore::QNodeId>() << m_value1Id << m_value2Id; + } + + using ClipBlendNode::setClipResults; + + double duration() const Q_DECL_FINAL { return 0.0f; } + +protected: + ClipResults doBlend(const QVector<ClipResults> &blendData) const Q_DECL_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); + } +} + +} // anonymous + + +class tst_AnimationUtils : public Qt3DCore::QBackendNodeTester +{ + Q_OBJECT + +public: + ChannelMapping *createChannelMapping(Handler *handler, + const QString &channelName, + const Qt3DCore::QNodeId targetId, + const QString &property, + const char *propertyName, + int type) + { + auto channelMappingId = Qt3DCore::QNodeId::createId(); + ChannelMapping *channelMapping = handler->channelMappingManager()->getOrCreateResource(channelMappingId); + setPeerId(channelMapping, channelMappingId); + channelMapping->setTargetId(targetId); + channelMapping->setProperty(property); + channelMapping->setPropertyName(propertyName); + channelMapping->setChannelName(channelName); + channelMapping->setType(type); + 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; + } + +private Q_SLOTS: + void checkBuildPropertyMappings_data() + { + QTest::addColumn<Handler *>("handler"); + QTest::addColumn<QVector<ChannelMapping *>>("channelMappings"); + QTest::addColumn<ChannelMapper *>("channelMapper"); + QTest::addColumn<AnimationClip *>("clip"); + QTest::addColumn<QVector<MappingData>>("expectedMappingData"); + + auto handler = new Handler; + auto channelMapping = createChannelMapping(handler, + QLatin1String("Location"), + Qt3DCore::QNodeId::createId(), + QLatin1String("translation"), + "translation", + static_cast<int>(QVariant::Vector3D)); + QVector<ChannelMapping *> channelMappings; + channelMappings.push_back(channelMapping); + + // ... a channel mapper... + auto channelMapper = createChannelMapper(handler, QVector<Qt3DCore::QNodeId>() << channelMapping->peerId()); + + // ...and an animation clip + auto clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json")); + + QVector<MappingData> mappingData; + MappingData mapping; + mapping.targetId = channelMapping->targetId(); + mapping.propertyName = channelMapping->propertyName(); // Location + mapping.type = channelMapping->type(); + mapping.channelIndices = QVector<int>() << 0 << 1 << 2; // Location X, Y, Z + mappingData.push_back(mapping); + + QTest::newRow("clip1.json") << handler + << channelMappings + << channelMapper + << clip + << mappingData; + } + + void checkBuildPropertyMappings() + { + // GIVEN + QFETCH(Handler *, handler); + QFETCH(QVector<ChannelMapping *>, channelMappings); + QFETCH(ChannelMapper *, channelMapper); + QFETCH(AnimationClip *, clip); + QFETCH(QVector<MappingData>, expectedMappingData); + + // WHEN + // Build the mapping data for the above configuration + QVector<MappingData> mappingData = buildPropertyMappings(handler, clip, channelMapper); + + // THEN + QCOMPARE(mappingData.size(), expectedMappingData.size()); + for (int i = 0; i < mappingData.size(); ++i) { + const auto mapping = mappingData[i]; + const auto expectedMapping = expectedMappingData[i]; + + QCOMPARE(mapping.targetId, expectedMapping.targetId); + QCOMPARE(mapping.propertyName, expectedMapping.propertyName); + QCOMPARE(mapping.type, expectedMapping.type); + QCOMPARE(mapping.channelIndices.size(), expectedMapping.channelIndices.size()); + for (int j = 0; j < mapping.channelIndices.size(); ++j) { + QCOMPARE(mapping.channelIndices[j], expectedMapping.channelIndices[j]); + } + } + + // Cleanup + delete handler; + } + + void checkBuildPropertyMappings2_data() + { + QTest::addColumn<QVector<ChannelMapping *>>("channelMappings"); + QTest::addColumn<QVector<ChannelNameAndType>>("channelNamesAndTypes"); + QTest::addColumn<QVector<ComponentIndices>>("channelComponentIndices"); + QTest::addColumn<QVector<MappingData>>("expectedResults"); + + // Single ChannelMapping + { + auto channelMapping = new ChannelMapping(); + channelMapping->setChannelName("Location"); + channelMapping->setTargetId(Qt3DCore::QNodeId::createId()); + channelMapping->setProperty(QLatin1String("translation")); + channelMapping->setPropertyName("translation"); + channelMapping->setType(static_cast<int>(QVariant::Vector3D)); + + QVector<ChannelMapping *> channelMappings = { channelMapping }; + + // Create a few channels in the format description + ChannelNameAndType rotation = { QLatin1String("Rotation"), + static_cast<int>(QVariant::Quaternion) }; + ChannelNameAndType location = { QLatin1String("Location"), + static_cast<int>(QVariant::Vector3D) }; + ChannelNameAndType baseColor = { QLatin1String("BaseColor"), + static_cast<int>(QVariant::Vector3D) }; + ChannelNameAndType metalness = { QLatin1String("Metalness"), + static_cast<int>(QVariant::Double) }; + ChannelNameAndType roughness = { QLatin1String("Roughness"), + static_cast<int>(QVariant::Double) }; + QVector<ChannelNameAndType> channelNamesAndTypes + = { rotation, location, baseColor, metalness, roughness }; + + // 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 }; + QVector<ComponentIndices> channelComponentIndices + = { rotationIndices, locationIndices, baseColorIndices, + metalnessIndices, roughnessIndices }; + + 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") + << channelMappings + << channelNamesAndTypes + << channelComponentIndices + << expectedResults; + } + + // Multiple ChannelMappings + { + auto locationMapping = new ChannelMapping(); + locationMapping->setChannelName("Location"); + locationMapping->setTargetId(Qt3DCore::QNodeId::createId()); + locationMapping->setProperty(QLatin1String("translation")); + locationMapping->setPropertyName("translation"); + locationMapping->setType(static_cast<int>(QVariant::Vector3D)); + + auto metalnessMapping = new ChannelMapping(); + metalnessMapping->setChannelName("Metalness"); + metalnessMapping->setTargetId(Qt3DCore::QNodeId::createId()); + metalnessMapping->setProperty(QLatin1String("metalness")); + metalnessMapping->setPropertyName("metalness"); + metalnessMapping->setType(static_cast<int>(QVariant::Double)); + + auto baseColorMapping = new ChannelMapping(); + baseColorMapping->setChannelName("BaseColor"); + baseColorMapping->setTargetId(Qt3DCore::QNodeId::createId()); + baseColorMapping->setProperty(QLatin1String("baseColor")); + baseColorMapping->setPropertyName("baseColor"); + baseColorMapping->setType(static_cast<int>(QVariant::Vector3D)); + + auto roughnessMapping = new ChannelMapping(); + roughnessMapping->setChannelName("Roughness"); + roughnessMapping->setTargetId(Qt3DCore::QNodeId::createId()); + roughnessMapping->setProperty(QLatin1String("roughness")); + roughnessMapping->setPropertyName("roughness"); + roughnessMapping->setType(static_cast<int>(QVariant::Double)); + + auto rotationMapping = new ChannelMapping(); + rotationMapping->setChannelName("Rotation"); + rotationMapping->setTargetId(Qt3DCore::QNodeId::createId()); + rotationMapping->setProperty(QLatin1String("rotation")); + rotationMapping->setPropertyName("rotation"); + rotationMapping->setType(static_cast<int>(QVariant::Quaternion)); + + QVector<ChannelMapping *> channelMappings + = { locationMapping, metalnessMapping, + baseColorMapping, roughnessMapping, + rotationMapping }; + + // Create a few channels in the format description + ChannelNameAndType rotation = { QLatin1String("Rotation"), + static_cast<int>(QVariant::Quaternion) }; + ChannelNameAndType location = { QLatin1String("Location"), + static_cast<int>(QVariant::Vector3D) }; + ChannelNameAndType baseColor = { QLatin1String("BaseColor"), + static_cast<int>(QVariant::Vector3D) }; + ChannelNameAndType metalness = { QLatin1String("Metalness"), + static_cast<int>(QVariant::Double) }; + ChannelNameAndType roughness = { QLatin1String("Roughness"), + static_cast<int>(QVariant::Double) }; + QVector<ChannelNameAndType> channelNamesAndTypes + = { rotation, location, baseColor, metalness, roughness }; + + // 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 }; + QVector<ComponentIndices> channelComponentIndices + = { rotationIndices, locationIndices, baseColorIndices, + metalnessIndices, roughnessIndices }; + + 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; + + QVector<MappingData> expectedResults + = { expectedLocationMapping, + expectedMetalnessMapping, + expectedBaseColorMapping, + expectedRoughnessMapping, + expectedRotationMapping }; + + QTest::newRow("multiple mappings") + << channelMappings + << channelNamesAndTypes + << channelComponentIndices + << expectedResults; + } + } + + void checkBuildPropertyMappings2() + { + // GIVEN + QFETCH(QVector<ChannelMapping *>, channelMappings); + QFETCH(QVector<ChannelNameAndType>, channelNamesAndTypes); + QFETCH(QVector<ComponentIndices>, channelComponentIndices); + QFETCH(QVector<MappingData>, expectedResults); + + // WHEN + const QVector<MappingData> actualResults = buildPropertyMappings(channelMappings, + channelNamesAndTypes, + channelComponentIndices); + + // 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.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]); + } + } + } + + void checkLocalTimeFromGlobalTime_data() + { + QTest::addColumn<double>("globalTime"); + QTest::addColumn<double>("globalStartTime"); + QTest::addColumn<double>("playbackRate"); + QTest::addColumn<double>("duration"); + QTest::addColumn<int>("loopCount"); + QTest::addColumn<double>("expectedLocalTime"); + QTest::addColumn<int>("expectedCurrentLoop"); + + double globalTime; + double globalStartTime; + double playbackRate; + double duration; + int loopCount; + double expectedLocalTime; + int expectedCurrentLoop; + + globalTime = 0.0; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 1; + expectedLocalTime = 0.0; + expectedCurrentLoop = 0; + QTest::newRow("simple, t_global = 0") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedLocalTime << expectedCurrentLoop; + + globalTime = 0.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 1; + expectedLocalTime = 0.5; + expectedCurrentLoop = 0; + QTest::newRow("simple, t_global = 0.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedLocalTime << expectedCurrentLoop; + + globalTime = 1.0; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 1; + expectedLocalTime = 1.0; + expectedCurrentLoop = 0; + QTest::newRow("simple, t_global = 1.0") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedLocalTime << expectedCurrentLoop; + + globalTime = -0.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 1; + expectedLocalTime = 0.0; + expectedCurrentLoop = 0; + QTest::newRow("simple, t_global = -0.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedLocalTime << expectedCurrentLoop; + + globalTime = 1.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 1; + expectedLocalTime = 1.0; + expectedCurrentLoop = 0; + QTest::newRow("simple, t_global = 1.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedLocalTime << expectedCurrentLoop; + + globalTime = 0.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 2; + expectedLocalTime = 0.5; + expectedCurrentLoop = 0; + QTest::newRow("simple, loopCount = 2, t_global = 0.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedLocalTime << expectedCurrentLoop; + + globalTime = 1.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 2; + expectedLocalTime = 0.5; + expectedCurrentLoop = 1; + QTest::newRow("simple, loopCount = 2, t_global = 1.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedLocalTime << expectedCurrentLoop; + + globalTime = 3.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 2.0; + loopCount = 2; + expectedLocalTime = 1.5; + expectedCurrentLoop = 1; + QTest::newRow("duration = 2, loopCount = 2, t_global = 3.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedLocalTime << expectedCurrentLoop; + + globalTime = 4.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 2.0; + loopCount = 2; + expectedLocalTime = 2.0; + expectedCurrentLoop = 1; + QTest::newRow("duration = 2, loopCount = 2, t_global = 4.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedLocalTime << expectedCurrentLoop; + + globalTime = 1.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 0; + expectedLocalTime = 0.5; + expectedCurrentLoop = 1; + QTest::newRow("simple, loopCount = inf, t_global = 1.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedLocalTime << expectedCurrentLoop; + + globalTime = 10.2; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 0; + expectedLocalTime = 0.2; + expectedCurrentLoop = 10; + QTest::newRow("simple, loopCount = inf, t_global = 10.2") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedLocalTime << expectedCurrentLoop; + } + + void checkLocalTimeFromGlobalTime() + { + // GIVEN + QFETCH(double, globalTime); + QFETCH(double, globalStartTime); + QFETCH(double, playbackRate); + QFETCH(double, duration); + QFETCH(int, loopCount); + QFETCH(double, expectedLocalTime); + QFETCH(int, expectedCurrentLoop); + + // WHEN + int actualCurrentLoop = 0; + double actualLocalTime = localTimeFromGlobalTime(globalTime, + globalStartTime, + playbackRate, + duration, + loopCount, + actualCurrentLoop); + + // THEN + QCOMPARE(actualCurrentLoop, expectedCurrentLoop); + QCOMPARE(actualLocalTime, expectedLocalTime); + } + + void checkPhaseFromGlobalTime_data() + { + QTest::addColumn<double>("globalTime"); + QTest::addColumn<double>("globalStartTime"); + QTest::addColumn<double>("playbackRate"); + QTest::addColumn<double>("duration"); + QTest::addColumn<int>("loopCount"); + QTest::addColumn<double>("expectedPhase"); + QTest::addColumn<int>("expectedCurrentLoop"); + + double globalTime; + double globalStartTime; + double playbackRate; + double duration; + int loopCount; + double expectedPhase; + int expectedCurrentLoop; + + globalTime = 0.0; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 1; + expectedPhase = 0.0; + expectedCurrentLoop = 0; + QTest::newRow("simple, t_global = 0") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedPhase << expectedCurrentLoop; + + globalTime = 0.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 1; + expectedPhase = 0.5; + expectedCurrentLoop = 0; + QTest::newRow("simple, t_global = 0.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedPhase << expectedCurrentLoop; + + globalTime = 1.0; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 1; + expectedPhase = 1.0; + expectedCurrentLoop = 0; + QTest::newRow("simple, t_global = 1.0") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedPhase << expectedCurrentLoop; + + globalTime = -0.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 1; + expectedPhase = 0.0; + expectedCurrentLoop = 0; + QTest::newRow("simple, t_global = -0.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedPhase << expectedCurrentLoop; + + globalTime = 1.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 1; + expectedPhase = 1.0; + expectedCurrentLoop = 0; + QTest::newRow("simple, t_global = 1.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedPhase << expectedCurrentLoop; + + globalTime = 0.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 2; + expectedPhase = 0.5; + expectedCurrentLoop = 0; + QTest::newRow("simple, loopCount = 2, t_global = 0.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedPhase << expectedCurrentLoop; + + globalTime = 1.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 2; + expectedPhase = 0.5; + expectedCurrentLoop = 1; + QTest::newRow("simple, loopCount = 2, t_global = 1.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedPhase << expectedCurrentLoop; + + globalTime = 3.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 2.0; + loopCount = 2; + expectedPhase = 0.75; + expectedCurrentLoop = 1; + QTest::newRow("duration = 2, loopCount = 2, t_global = 3.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedPhase << expectedCurrentLoop; + + globalTime = 4.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 2.0; + loopCount = 2; + expectedPhase = 1.0; + expectedCurrentLoop = 1; + QTest::newRow("duration = 2, loopCount = 2, t_global = 4.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedPhase << expectedCurrentLoop; + + globalTime = 1.5; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 0; + expectedPhase = 0.5; + expectedCurrentLoop = 1; + QTest::newRow("simple, loopCount = inf, t_global = 1.5") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedPhase << expectedCurrentLoop; + + globalTime = 10.2; + globalStartTime = 0.0; + playbackRate = 1.0; + duration = 1.0; + loopCount = 0; + expectedPhase = 0.2; + expectedCurrentLoop = 10; + QTest::newRow("simple, loopCount = inf, t_global = 10.2") + << globalTime << globalStartTime << playbackRate << duration << loopCount + << expectedPhase << expectedCurrentLoop; + } + + void checkPhaseFromGlobalTime() + { + // GIVEN + QFETCH(double, globalTime); + QFETCH(double, globalStartTime); + QFETCH(double, playbackRate); + QFETCH(double, duration); + QFETCH(int, loopCount); + QFETCH(double, expectedPhase); + QFETCH(int, expectedCurrentLoop); + + // WHEN + int actualCurrentLoop = 0; + double actualPhase = phaseFromGlobalTime(globalTime, + globalStartTime, + playbackRate, + duration, + loopCount, + actualCurrentLoop); + + // THEN + QCOMPARE(actualCurrentLoop, expectedCurrentLoop); + QCOMPARE(actualPhase, expectedPhase); + } + + void checkPreparePropertyChanges_data() + { + QTest::addColumn<Qt3DCore::QNodeId>("animatorId"); + QTest::addColumn<QVector<MappingData>>("mappingData"); + QTest::addColumn<QVector<float>>("channelResults"); + QTest::addColumn<bool>("finalFrame"); + QTest::addColumn<QVector<Qt3DCore::QPropertyUpdatedChangePtr>>("expectedChanges"); + + Qt3DCore::QNodeId animatorId; + QVector<MappingData> mappingData; + QVector<float> channelResults; + bool finalFrame; + QVector<Qt3DCore::QPropertyUpdatedChangePtr> 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; + finalFrame = false; + + auto change = Qt3DCore::QPropertyUpdatedChangePtr::create(mapping.targetId); + change->setDeliveryFlags(Qt3DCore::QSceneChange::DeliverToAll); + change->setPropertyName(mapping.propertyName); + change->setValue(QVariant::fromValue(QVector3D(1.0f, 2.0f, 3.0f))); + expectedChanges.push_back(change); + + QTest::newRow("vec3 translation, final = false") + << animatorId << mappingData << channelResults << finalFrame + << expectedChanges; + + finalFrame = true; + auto animatorChange = Qt3DCore::QPropertyUpdatedChangePtr::create(animatorId); + animatorChange->setDeliveryFlags(Qt3DCore::QSceneChange::DeliverToAll); + animatorChange->setPropertyName("running"); + animatorChange->setValue(false); + expectedChanges.push_back(animatorChange); + + QTest::newRow("vec3 translation, final = true") + << animatorId << mappingData << channelResults << finalFrame + << expectedChanges; + + mappingData.clear(); + channelResults.clear(); + expectedChanges.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; + finalFrame = false; + + auto translationChange = Qt3DCore::QPropertyUpdatedChangePtr::create(translationMapping.targetId); + translationChange->setDeliveryFlags(Qt3DCore::QSceneChange::DeliverToAll); + translationChange->setPropertyName(translationMapping.propertyName); + translationChange->setValue(QVariant::fromValue(QVector3D(1.0f, 2.0f, 3.0f))); + expectedChanges.push_back(translationChange); + + auto scaleChange = Qt3DCore::QPropertyUpdatedChangePtr::create(scaleMapping.targetId); + scaleChange->setDeliveryFlags(Qt3DCore::QSceneChange::DeliverToAll); + scaleChange->setPropertyName(scaleMapping.propertyName); + scaleChange->setValue(QVariant::fromValue(QVector3D(4.0f, 5.0f, 6.0f))); + expectedChanges.push_back(scaleChange); + + QTest::newRow("vec3 translation, vec3 scale, final = false") + << animatorId << mappingData << channelResults << finalFrame + << expectedChanges; + + finalFrame = true; + auto animatorChange = Qt3DCore::QPropertyUpdatedChangePtr::create(animatorId); + animatorChange->setDeliveryFlags(Qt3DCore::QSceneChange::DeliverToAll); + animatorChange->setPropertyName("running"); + animatorChange->setValue(false); + expectedChanges.push_back(animatorChange); + + QTest::newRow("vec3 translation, vec3 scale, final = true") + << animatorId << mappingData << channelResults << finalFrame + << expectedChanges; + + mappingData.clear(); + channelResults.clear(); + expectedChanges.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; + finalFrame = false; + + auto change = Qt3DCore::QPropertyUpdatedChangePtr::create(mapping.targetId); + change->setDeliveryFlags(Qt3DCore::QSceneChange::DeliverToAll); + change->setPropertyName(mapping.propertyName); + change->setValue(QVariant::fromValue(3.5f)); + expectedChanges.push_back(change); + + QTest::newRow("double mass") + << animatorId << mappingData << channelResults << finalFrame + << expectedChanges; + + mappingData.clear(); + channelResults.clear(); + expectedChanges.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; + finalFrame = false; + + auto change = Qt3DCore::QPropertyUpdatedChangePtr::create(mapping.targetId); + change->setDeliveryFlags(Qt3DCore::QSceneChange::DeliverToAll); + change->setPropertyName(mapping.propertyName); + change->setValue(QVariant::fromValue(QVector2D(2.0f, 1.0f))); + expectedChanges.push_back(change); + + QTest::newRow("vec2 pos") + << animatorId << mappingData << channelResults << finalFrame + << expectedChanges; + + mappingData.clear(); + channelResults.clear(); + expectedChanges.clear(); + } + + // Single property, vec4 + { + animatorId = Qt3DCore::QNodeId::createId(); + MappingData mapping; + mapping.targetId = Qt3DCore::QNodeId::createId(); + mapping.propertyName = "foo"; + mapping.type = static_cast<int>(QVariant::Vector2D); + mapping.channelIndices = QVector<int>() << 0 << 1 << 2 << 3; + mappingData.push_back(mapping); + channelResults = QVector<float>() << 4.0f << 3.0f << 2.0f << 1.0f; + finalFrame = false; + + auto change = Qt3DCore::QPropertyUpdatedChangePtr::create(mapping.targetId); + change->setDeliveryFlags(Qt3DCore::QSceneChange::DeliverToAll); + change->setPropertyName(mapping.propertyName); + change->setValue(QVariant::fromValue(QVector4D(4.0f, 3.0f, 2.0f, 1.0f))); + expectedChanges.push_back(change); + + QTest::newRow("vec4 foo") + << animatorId << mappingData << channelResults << finalFrame + << expectedChanges; + + mappingData.clear(); + channelResults.clear(); + expectedChanges.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; + finalFrame = false; + + auto change = Qt3DCore::QPropertyUpdatedChangePtr::create(mapping.targetId); + change->setDeliveryFlags(Qt3DCore::QSceneChange::DeliverToAll); + change->setPropertyName(mapping.propertyName); + change->setValue(QVariant::fromValue(QQuaternion(1.0f, 0.0f, 0.0f, 1.0f))); + expectedChanges.push_back(change); + + QTest::newRow("quaternion rotation") + << animatorId << mappingData << channelResults << finalFrame + << expectedChanges; + + mappingData.clear(); + channelResults.clear(); + expectedChanges.clear(); + } + } + + void checkPreparePropertyChanges() + { + // GIVEN + QFETCH(Qt3DCore::QNodeId, animatorId); + QFETCH(QVector<MappingData>, mappingData); + QFETCH(QVector<float>, channelResults); + QFETCH(bool, finalFrame); + QFETCH(QVector<Qt3DCore::QPropertyUpdatedChangePtr>, expectedChanges); + + // WHEN + QVector<Qt3DCore::QSceneChangePtr> actualChanges + = preparePropertyChanges(animatorId, mappingData, channelResults, finalFrame); + + // THEN + QCOMPARE(actualChanges.size(), expectedChanges.size()); + for (int i = 0; i < actualChanges.size(); ++i) { + auto expectedChange = expectedChanges[i]; + auto actualChange + = qSharedPointerCast<Qt3DCore::QPropertyUpdatedChange>(expectedChanges[i]); + + QCOMPARE(actualChange->subjectId(), expectedChange->subjectId()); + QCOMPARE(actualChange->deliveryFlags(), expectedChange->deliveryFlags()); + QCOMPARE(actualChange->propertyName(), expectedChange->propertyName()); + QCOMPARE(actualChange->value(), expectedChange->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(); + } + } + + 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>("offset"); + QTest::addColumn<QVector<char>>("suffixes"); + QTest::addColumn<QVector<int>>("expectedResults"); + + Channel channel; + int dataType; + int offset; + QVector<char> suffixes; + QVector<int> expectedResults; + + // 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); + offset = 0; + suffixes = (QVector<char>() << 'X' << 'Y' << 'Z' << 'W'); + expectedResults = (QVector<int>() << 0 << 1 << 2); + + QTest::newRow("vec3 location, offset = 0") + << channel << dataType << offset << suffixes << expectedResults; + + expectedResults.clear(); + + offset = 4; + expectedResults = (QVector<int>() << 4 << 5 << 6); + QTest::newRow("vec3 location, offset = 4") + << channel << dataType << 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); + offset = 0; + suffixes = (QVector<char>() << 'X' << 'Y' << 'Z' << 'W'); + expectedResults = (QVector<int>() << 0 << 1); + + QTest::newRow("vec2 pos, offset = 0") + << channel << dataType << offset << suffixes << expectedResults; + + expectedResults.clear(); + + offset = 2; + expectedResults = (QVector<int>() << 2 << 3); + QTest::newRow("vec2 pos, offset = 2") + << channel << dataType << 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); + offset = 0; + suffixes = (QVector<char>() << 'X' << 'Y' << 'Z' << 'W'); + expectedResults = (QVector<int>() << 0 << 1 << 2 << 3); + + QTest::newRow("vec4 foo, offset = 0") + << channel << dataType << offset << suffixes << expectedResults; + + expectedResults.clear(); + + offset = 10; + expectedResults = (QVector<int>() << 10 << 11 << 12 << 13); + QTest::newRow("vec4 foo, offset = 10") + << channel << dataType << 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); + offset = 0; + suffixes = (QVector<char>() << 'X' << 'Y' << 'Z' << 'W'); + expectedResults = (QVector<int>() << 0); + + QTest::newRow("double Mass, offset = 0") + << channel << dataType << offset << suffixes << expectedResults; + + expectedResults.clear(); + + offset = 5; + expectedResults = (QVector<int>() << 5); + QTest::newRow("double Mass, offset = 5") + << channel << dataType << 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); + offset = 0; + suffixes = (QVector<char>() << 'W' << 'X' << 'Y' << 'Z'); + expectedResults = (QVector<int>() << 0 << 1 << 2 << 3); + + QTest::newRow("quaternion Rotation, offset = 0") + << channel << dataType << offset << suffixes << expectedResults; + + expectedResults.clear(); + + offset = 10; + expectedResults = (QVector<int>() << 10 << 11 << 12 << 13); + QTest::newRow("quaternion Rotation, offset = 10") + << channel << dataType << 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); + 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 << offset << suffixes << expectedResults; + + expectedResults.clear(); + + offset = 10; + expectedResults = (QVector<int>() << 11 << 10 << 13 << 12); + QTest::newRow("quaternion Rotation, offset = 10, randomized") + << channel << dataType << offset << suffixes << expectedResults; + + suffixes.clear(); + expectedResults.clear(); + } + } + + void checkChannelComponentsToIndicesHelper() + { + // GIVEN + QFETCH(Channel, channel); + QFETCH(int, dataType); + QFETCH(int, offset); + QFETCH(QVector<char>, suffixes); + QFETCH(QVector<int>, expectedResults); + + // WHEN + QVector<int> actualResults + = channelComponentsToIndicesHelper(channel, dataType, 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>("offset"); + QTest::addColumn<QVector<int>>("expectedResults"); + + Channel channel; + int dataType; + 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); + offset = 0; + expectedResults = (QVector<int>() << 0 << 1 << 2 << 3); + + QTest::newRow("quaternion Rotation, offset = 0") + << channel << dataType << offset << expectedResults; + + expectedResults.clear(); + + offset = 10; + expectedResults = (QVector<int>() << 10 << 11 << 12 << 13); + QTest::newRow("quaternion Rotation, offset = 10") + << channel << dataType << 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); + offset = 0; + expectedResults = (QVector<int>() << 0 << 1 << 2); + + QTest::newRow("vec3 location, offset = 0") + << channel << dataType << offset << expectedResults; + + expectedResults.clear(); + + offset = 4; + expectedResults = (QVector<int>() << 4 << 5 << 6); + QTest::newRow("vec3 location, offset = 4") + << channel << dataType << offset << expectedResults; + + expectedResults.clear(); + } + } + + void checkChannelComponentsToIndices() + { + QFETCH(Channel, channel); + QFETCH(int, dataType); + QFETCH(int, offset); + QFETCH(QVector<int>, expectedResults); + + // WHEN + QVector<int> actualResults + = channelComponentsToIndices(channel, dataType, 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; + + { + handler = new Handler(); + clip = createAnimationClipLoader(handler, QUrl("qrc:/clip1.json")); + const qint64 globalStartTimeNS = 0; + const int loops = 1; + auto animator = createClipAnimator(handler, globalStartTimeNS, loops); + const qint64 globalTimeNS = 0; + animatorData = evaluationDataForAnimator(animator, globalTimeNS); // Tested elsewhere + + clipData.localTime = localTimeFromGlobalTime(animatorData.globalTime, + animatorData.startTime, + 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); + const qint64 globalTimeNS = (clip->duration() + 1.0) * 1.0e9; // +1 to ensure beyond end of clip + animatorData = evaluationDataForAnimator(animator, globalTimeNS); // Tested elsewhere + + clipData.localTime = localTimeFromGlobalTime(animatorData.globalTime, + animatorData.startTime, + animatorData.playbackRate, + clip->duration(), + animatorData.loopCount, + clipData.currentLoop); // Tested elsewhere + clipData.isFinalFrame = true; + + QTest::newRow("clip1.json, globalTime = duration") + << 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); + const qint64 globalTimeNS = 2.0 * clip->duration() * 1.0e9; + animatorData = evaluationDataForAnimator(animator, globalTimeNS); // Tested elsewhere + + clipData.localTime = localTimeFromGlobalTime(animatorData.globalTime, + animatorData.startTime, + animatorData.playbackRate, + clip->duration(), + animatorData.loopCount, + clipData.currentLoop); // Tested elsewhere + clipData.isFinalFrame = false; + + QTest::newRow("clip1.json, globalTime = 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); + const qint64 globalTimeNS = (2.0 * clip->duration() + 1.0) * 1.0e9; // +1 to ensure beyond end of clip + animatorData = evaluationDataForAnimator(animator, globalTimeNS); // Tested elsewhere + + clipData.localTime = localTimeFromGlobalTime(animatorData.globalTime, + animatorData.startTime, + animatorData.playbackRate, + clip->duration(), + animatorData.loopCount, + clipData.currentLoop); // Tested elsewhere + clipData.isFinalFrame = true; + + QTest::newRow("clip1.json, globalTime = 2 * duration + 1, loops = 2") + << 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>("globalTimeNS"); + QTest::addColumn<AnimatorEvaluationData>("expectedAnimatorData"); + + Handler *handler; + ClipAnimator *animator; + qint64 globalTimeNS; + AnimatorEvaluationData expectedAnimatorData; + + { + handler = new Handler(); + const qint64 globalStartTimeNS = 0; + const int loops = 1; + animator = createClipAnimator(handler, globalStartTimeNS, loops); + globalTimeNS = 0; + + expectedAnimatorData.loopCount = loops; + expectedAnimatorData.playbackRate = 1.0; // hard-wired for now + expectedAnimatorData.startTime = 0.0; + expectedAnimatorData.globalTime = 0.0; + + QTest::newRow("globalStartTime = 0, globalTime = 0, loops = 1") + << handler << animator << globalTimeNS << expectedAnimatorData; + } + + { + handler = new Handler(); + const qint64 globalStartTimeNS = 0; + const int loops = 5; + animator = createClipAnimator(handler, globalStartTimeNS, loops); + globalTimeNS = 0; + + expectedAnimatorData.loopCount = loops; + expectedAnimatorData.playbackRate = 1.0; // hard-wired for now + expectedAnimatorData.startTime = 0.0; + expectedAnimatorData.globalTime = 0.0; + + QTest::newRow("globalStartTime = 0, globalTime = 0, loops = 5") + << handler << animator << globalTimeNS << expectedAnimatorData; + } + + { + handler = new Handler(); + const qint64 globalStartTimeNS = 0; + const int loops = 1; + animator = createClipAnimator(handler, globalStartTimeNS, loops); + globalTimeNS = 5000000000; + + expectedAnimatorData.loopCount = loops; + expectedAnimatorData.playbackRate = 1.0; // hard-wired for now + expectedAnimatorData.startTime = 0.0; + expectedAnimatorData.globalTime = 5.0; + + QTest::newRow("globalStartTime = 0, globalTime = 5, loops = 1") + << handler << animator << globalTimeNS << expectedAnimatorData; + } + + { + handler = new Handler(); + const qint64 globalStartTimeNS = 3000000000; + const int loops = 1; + animator = createClipAnimator(handler, globalStartTimeNS, loops); + globalTimeNS = 5000000000; + + expectedAnimatorData.loopCount = loops; + expectedAnimatorData.playbackRate = 1.0; // hard-wired for now + expectedAnimatorData.startTime = 3.0; + expectedAnimatorData.globalTime = 5.0; + + QTest::newRow("globalStartTime = 3, globalTime = 5, loops = 1") + << handler << animator << globalTimeNS << expectedAnimatorData; + } + } + + void checkEvaluationDataForAnimator() + { + // GIVEN + QFETCH(Handler *, handler); + QFETCH(ClipAnimator *, animator); + QFETCH(qint64, globalTimeNS); + QFETCH(AnimatorEvaluationData, expectedAnimatorData); + + // WHEN + AnimatorEvaluationData actualAnimatorData = evaluationDataForAnimator(animator, globalTimeNS); + + // THEN + QCOMPARE(actualAnimatorData.loopCount, expectedAnimatorData.loopCount); + QVERIFY(fuzzyCompare(actualAnimatorData.playbackRate, expectedAnimatorData.playbackRate) == true); + QVERIFY(fuzzyCompare(actualAnimatorData.startTime, expectedAnimatorData.startTime) == true); + QVERIFY(fuzzyCompare(actualAnimatorData.globalTime, expectedAnimatorData.globalTime) == 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; + } + } + + 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(), + QLatin1String("translation"), + "translation", + static_cast<int>(QVariant::Vector3D)); + 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) }); + + QTest::addRow("Location, vec3") << handler << channelMapper << expectedResults; + } + + { + auto handler = new Handler(); + auto channelMapping1 = createChannelMapping(handler, + QLatin1String("Location"), + Qt3DCore::QNodeId::createId(), + QLatin1String("translation"), + "translation", + static_cast<int>(QVariant::Vector3D)); + auto channelMapping2 = createChannelMapping(handler, + QLatin1String("Rotation"), + Qt3DCore::QNodeId::createId(), + QLatin1String("rotatrion"), + "rotation", + static_cast<int>(QVariant::Quaternion)); + 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) }); + expectedResults.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion) }); + + QTest::addRow("Multiple unique channels") << handler << channelMapper << expectedResults; + } + + { + auto handler = new Handler(); + auto channelMapping1 = createChannelMapping(handler, + QLatin1String("Location"), + Qt3DCore::QNodeId::createId(), + QLatin1String("translation"), + "translation", + static_cast<int>(QVariant::Vector3D)); + auto channelMapping2 = createChannelMapping(handler, + QLatin1String("Rotation"), + Qt3DCore::QNodeId::createId(), + QLatin1String("rotation"), + "rotation", + static_cast<int>(QVariant::Quaternion)); + auto channelMapping3 = createChannelMapping(handler, + QLatin1String("Location"), + Qt3DCore::QNodeId::createId(), + QLatin1String("translation"), + "translation", + static_cast<int>(QVariant::Vector3D)); + auto channelMapping4 = createChannelMapping(handler, + QLatin1String("Location"), + Qt3DCore::QNodeId::createId(), + QLatin1String("translation"), + "translation", + static_cast<int>(QVariant::Vector3D)); + + 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) }); + expectedResults.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion) }); + + QTest::addRow("Multiple channels with repeats") << 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) }); + + 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) }); + allChannels.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion) }); + + 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) }); + allChannels.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion) }); + allChannels.push_back({ QLatin1String("BaseColor"), static_cast<int>(QVariant::Vector3D) }); + allChannels.push_back({ QLatin1String("Metalness"), static_cast<int>(QVariant::Double) }); + allChannels.push_back({ QLatin1String("Roughness"), static_cast<int>(QVariant::Double) }); + + 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 }); + + QTest::newRow("vec3 location, quaterion rotation, pbr metal-rough") << 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<ComponentIndices>("expectedResults"); + + { + QVector<ChannelNameAndType> targetChannels; + targetChannels.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion) }); + targetChannels.push_back({ QLatin1String("Location"), static_cast<int>(QVariant::Vector3D) }); + targetChannels.push_back({ QLatin1String("Base Color"), static_cast<int>(QVariant::Vector3D) }); + targetChannels.push_back({ QLatin1String("Metalness"), static_cast<int>(QVariant::Double) }); + targetChannels.push_back({ QLatin1String("Roughness"), static_cast<int>(QVariant::Double) }); + + 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(); + + ComponentIndices expectedResults = { 0, 1, 2, 3, // Rotation + 4, 5, 6, // Location + 7, 8, 9, // Base Color + 10, // Metalness + 11 }; // Roughness + + QTest::newRow("rotation, location, pbr metal-rough") + << targetChannels << targetIndices << clip << expectedResults; + } + + { + QVector<ChannelNameAndType> targetChannels; + targetChannels.push_back({ QLatin1String("Location"), static_cast<int>(QVariant::Vector3D) }); + targetChannels.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion) }); + targetChannels.push_back({ QLatin1String("Base Color"), static_cast<int>(QVariant::Vector3D) }); + targetChannels.push_back({ QLatin1String("Metalness"), static_cast<int>(QVariant::Double) }); + targetChannels.push_back({ QLatin1String("Roughness"), static_cast<int>(QVariant::Double) }); + + 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(); + + ComponentIndices expectedResults = { 4, 5, 6, // Location + 0, 1, 2, 3, // Rotation + 7, 8, 9, // Base Color + 10, // Metalness + 11 }; // Roughness + + QTest::newRow("location, rotation, pbr metal-rough") + << targetChannels << targetIndices << clip << expectedResults; + } + + { + QVector<ChannelNameAndType> targetChannels; + targetChannels.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion) }); + targetChannels.push_back({ QLatin1String("Location"), static_cast<int>(QVariant::Vector3D) }); + targetChannels.push_back({ QLatin1String("Albedo"), static_cast<int>(QVariant::Vector3D) }); + targetChannels.push_back({ QLatin1String("Metalness"), static_cast<int>(QVariant::Double) }); + targetChannels.push_back({ QLatin1String("Roughness"), static_cast<int>(QVariant::Double) }); + + 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(); + + ComponentIndices expectedResults = { 0, 1, 2, 3, // Rotation + 4, 5, 6, // Location + -1, -1, -1, // Albedo (missing from clip) + 10, // Metalness + 11 }; // Roughness + + 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) }); + targetChannels.push_back({ QLatin1String("Rotation"), static_cast<int>(QVariant::Quaternion) }); + targetChannels.push_back({ QLatin1String("Albedo"), static_cast<int>(QVariant::Vector3D) }); + targetChannels.push_back({ QLatin1String("Metalness"), static_cast<int>(QVariant::Double) }); + targetChannels.push_back({ QLatin1String("Roughness"), static_cast<int>(QVariant::Double) }); + + 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(); + + ComponentIndices expectedResults = { 4, 5, 6, // Location + 0, 1, 2, 3, // Rotation + -1, -1, -1, // Albedo (missing from clip) + 10, // Metalness + 11 }; // Roughness + + QTest::newRow("location, rotation, albedo (missing), metal-rough") + << targetChannels << targetIndices << clip << expectedResults; + } + } + + void checkGenerateClipFormatIndices() + { + // GIVEN + QFETCH(QVector<ChannelNameAndType>, targetChannels); + QFETCH(QVector<ComponentIndices>, targetIndices); + QFETCH(AnimationClip *, clip); + QFETCH(ComponentIndices, expectedResults); + + // WHEN + const ComponentIndices actualResults = generateClipFormatIndices(targetChannels, + targetIndices, + clip); + + // THEN + QCOMPARE(actualResults.size(), expectedResults.size()); + for (int i = 0; i < actualResults.size(); ++i) + QCOMPARE(actualResults[i], expectedResults[i]); + + // Cleanup + delete clip; + } +}; + +QTEST_MAIN(tst_AnimationUtils) + +#include "tst_animationutils.moc" |