summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Lemire <paul.lemire@kdab.com>2017-01-29 09:13:56 +0100
committerSean Harmer <sean.harmer@kdab.com>2017-01-29 18:56:11 +0000
commit89766b24b5f071eaf390eadce6ba635d423201e1 (patch)
treeb231e02518c4dce3f4e73eafaa5e4a5a242b3e5d
parentd555d63084ac13866a78772b43b4ac6b6d9bed9b (diff)
Build blend trees and execute them
Also added loops property to the blendedclipanimator as mandated by rebase. Note: only handles LERP with single node for now Change-Id: I91e071467c604279262ec04288bc7f8b2b19f4a1 Reviewed-by: Sean Harmer <sean.harmer@kdab.com>
-rw-r--r--src/animation/backend/animationutils_p.h15
-rw-r--r--src/animation/backend/backend.pri8
-rw-r--r--src/animation/backend/blendedclipanimator.cpp31
-rw-r--r--src/animation/backend/blendedclipanimator_p.h22
-rw-r--r--src/animation/backend/buildblendtreesjob.cpp144
-rw-r--r--src/animation/backend/buildblendtreesjob_p.h86
-rw-r--r--src/animation/backend/evaluateblendclipanimatorjob.cpp159
-rw-r--r--src/animation/backend/evaluateblendclipanimatorjob_p.h87
-rw-r--r--src/animation/backend/handler.cpp62
-rw-r--r--src/animation/backend/handler_p.h12
-rw-r--r--src/animation/frontend/qblendedclipanimator.cpp19
-rw-r--r--src/animation/frontend/qblendedclipanimator.h4
-rw-r--r--src/animation/frontend/qblendedclipanimator_p.h2
-rw-r--r--tests/auto/animation/blendedclipanimator/tst_blendedclipanimator.cpp27
-rw-r--r--tests/auto/animation/qblendedclipanimator/tst_qblendedclipanimator.cpp55
-rw-r--r--tests/manual/animation-keyframe-simple/main.qml1
16 files changed, 725 insertions, 9 deletions
diff --git a/src/animation/backend/animationutils_p.h b/src/animation/backend/animationutils_p.h
index af17f74bf..e7f019aee 100644
--- a/src/animation/backend/animationutils_p.h
+++ b/src/animation/backend/animationutils_p.h
@@ -69,9 +69,24 @@ public:
Qt3DCore::QNodeId targetId;
const char *propertyName;
int type;
+ int channelBaseIndex;
QVector<int> channelIndices;
};
+ struct BlendingMappingData {
+ Qt3DCore::QNodeId targetId;
+ const char *propertyName;
+ int type;
+ QVector<int> channelIndicesClip1;
+ QVector<int> channelIndicesClip2;
+
+ enum BlendAction {
+ NoBlending, // Use the channel from Clip1 only
+ ClipBlending, // Blending 2 clips sharing the same channel
+ };
+ BlendAction blendAction;
+ };
+
static double localTimeFromGlobalTime(double t_global, double t_start_global,
double playbackRate, double duration,
int loopCount, int &currentLoop);
diff --git a/src/animation/backend/backend.pri b/src/animation/backend/backend.pri
index 0fa09f6a5..b0b0feaf0 100644
--- a/src/animation/backend/backend.pri
+++ b/src/animation/backend/backend.pri
@@ -23,7 +23,9 @@ HEADERS += \
$$PWD/clipblendnode_p.h \
$$PWD/lerpblend_p.h \
$$PWD/clipblendnodevisitor_p.h \
- $$PWD/animationutils_p.h
+ $$PWD/animationutils_p.h \
+ $$PWD/buildblendtreesjob_p.h \
+ $$PWD/evaluateblendclipanimatorjob_p.h
SOURCES += \
$$PWD/animationclip.cpp \
@@ -44,4 +46,6 @@ SOURCES += \
$$PWD/lerpblend.cpp \
$$PWD/managers.cpp \
$$PWD/clipblendnodevisitor.cpp \
- $$PWD/animationutils.cpp
+ $$PWD/animationutils.cpp \
+ $$PWD/buildblendtreesjob.cpp \
+ $$PWD/evaluateblendclipanimatorjob.cpp
diff --git a/src/animation/backend/blendedclipanimator.cpp b/src/animation/backend/blendedclipanimator.cpp
index 6862de74f..1cb774f72 100644
--- a/src/animation/backend/blendedclipanimator.cpp
+++ b/src/animation/backend/blendedclipanimator.cpp
@@ -45,8 +45,11 @@ namespace Qt3DAnimation {
namespace Animation {
BlendedClipAnimator::BlendedClipAnimator()
- : BackendNode(ReadOnly)
+ : BackendNode(ReadWrite)
, m_running(false)
+ , m_startGlobalTime(0)
+ , m_currentLoop(0)
+ , m_loops(1)
{
}
@@ -57,7 +60,8 @@ void BlendedClipAnimator::initializeFromPeer(const Qt3DCore::QNodeCreatedChangeB
m_blendTreeRootId = data.blendTreeRootId;
m_mapperId = data.mapperId;
m_running = data.running;
- setDirty(Handler::ClipAnimatorDirty);
+ m_loops = data.loops;
+ setDirty(Handler::BlendedClipAnimatorDirty);
}
void BlendedClipAnimator::cleanup()
@@ -67,24 +71,39 @@ void BlendedClipAnimator::cleanup()
m_blendTreeRootId = Qt3DCore::QNodeId();
m_mapperId = Qt3DCore::QNodeId();
m_running = false;
+ m_startGlobalTime = 0;
+ m_currentLoop = 0;
+ m_loops = 1;
+ m_mappingData.clear();
}
void BlendedClipAnimator::setBlendTreeRootId(Qt3DCore::QNodeId blendTreeId)
{
m_blendTreeRootId = blendTreeId;
- setDirty(Handler::ClipAnimatorDirty);
+ setDirty(Handler::BlendedClipAnimatorDirty);
}
void BlendedClipAnimator::setMapperId(Qt3DCore::QNodeId mapperId)
{
m_mapperId = mapperId;
- setDirty(Handler::ClipAnimatorDirty);
+ setDirty(Handler::BlendedClipAnimatorDirty);
}
void BlendedClipAnimator::setRunning(bool running)
{
m_running = running;
- setDirty(Handler::ClipAnimatorDirty);
+ setDirty(Handler::BlendedClipAnimatorDirty);
+}
+
+void BlendedClipAnimator::setMappingData(const QVector<AnimationUtils::BlendingMappingData> mappingData)
+{
+ m_mappingData = mappingData;
+}
+
+void BlendedClipAnimator::sendPropertyChanges(const QVector<Qt3DCore::QSceneChangePtr> &changes)
+{
+ for (const Qt3DCore::QSceneChangePtr &change : changes)
+ notifyObservers(change);
}
Qt3DCore::QNodeId BlendedClipAnimator::blendTreeRootId() const
@@ -103,6 +122,8 @@ void BlendedClipAnimator::sceneChangeEvent(const Qt3DCore::QSceneChangePtr &e)
setMapperId(change->value().value<Qt3DCore::QNodeId>());
else if (change->propertyName() == QByteArrayLiteral("running"))
setRunning(change->value().toBool());
+ else if (change->propertyName() == QByteArrayLiteral("loops"))
+ m_loops = change->value().toInt();
break;
}
diff --git a/src/animation/backend/blendedclipanimator_p.h b/src/animation/backend/blendedclipanimator_p.h
index c6f437cf9..d0db13e53 100644
--- a/src/animation/backend/blendedclipanimator_p.h
+++ b/src/animation/backend/blendedclipanimator_p.h
@@ -49,6 +49,7 @@
//
#include <Qt3DAnimation/private/backendnode_p.h>
+#include <Qt3DAnimation/private/animationutils_p.h>
QT_BEGIN_NAMESPACE
@@ -69,15 +70,36 @@ public:
Qt3DCore::QNodeId mapperId() const { return m_mapperId; }
bool isRunning() const { return m_running; }
+ // Called by BuildBlendTreeJob
+ bool canRun() const { return !m_mapperId.isNull() && !m_blendTreeRootId.isNull() && m_running; }
+
void setBlendTreeRootId(Qt3DCore::QNodeId blendTreeRootId);
void setMapperId(Qt3DCore::QNodeId mapperId);
void setRunning(bool running);
+ void setMappingData(const QVector<AnimationUtils::BlendingMappingData> mappingData);
+ QVector<AnimationUtils::BlendingMappingData> mappingData() const { return m_mappingData; }
+
+ void setStartTime(qint64 globalTime) { m_startGlobalTime = globalTime; }
+ qint64 startTime() const { return m_startGlobalTime; }
+
+ int loops() const { return m_loops; }
+
+ int currentLoop() const { return m_currentLoop; }
+ void setCurrentLoop(int currentLoop) { m_currentLoop = currentLoop; }
+
+ void sendPropertyChanges(const QVector<Qt3DCore::QSceneChangePtr> &changes);
+
private:
void initializeFromPeer(const Qt3DCore::QNodeCreatedChangeBasePtr &change) Q_DECL_FINAL;
Qt3DCore::QNodeId m_blendTreeRootId;
Qt3DCore::QNodeId m_mapperId;
bool m_running;
+
+ qint64 m_startGlobalTime;
+ QVector<AnimationUtils::BlendingMappingData> m_mappingData;
+ int m_currentLoop;
+ int m_loops;
};
} // namespace Animation
diff --git a/src/animation/backend/buildblendtreesjob.cpp b/src/animation/backend/buildblendtreesjob.cpp
new file mode 100644
index 000000000..a8e969a39
--- /dev/null
+++ b/src/animation/backend/buildblendtreesjob.cpp
@@ -0,0 +1,144 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "buildblendtreesjob_p.h"
+#include <Qt3DAnimation/private/handler_p.h>
+#include <Qt3DAnimation/private/managers_p.h>
+#include <Qt3DAnimation/private/clipblendnodevisitor_p.h>
+#include <Qt3DAnimation/private/clipblendnode_p.h>
+#include <Qt3DAnimation/private/lerpblend_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DAnimation {
+namespace Animation {
+
+BuildBlendTreesJob::BuildBlendTreesJob()
+ : Qt3DCore::QAspectJob()
+{
+ // TO DO: Add Profiler ID
+}
+
+void BuildBlendTreesJob::setBlendedClipAnimators(const QVector<HBlendedClipAnimator> &blendedClipAnimatorHandles)
+{
+ m_blendedClipAnimatorHandles = blendedClipAnimatorHandles;
+}
+
+void BuildBlendTreesJob::run()
+{
+ for (const HBlendedClipAnimator blendedClipAnimatorHandle : m_blendedClipAnimatorHandles) {
+ // Retrive BlendTree node
+ BlendedClipAnimator *blendClipAnimator = m_handler->blendedClipAnimatorManager()->data(blendedClipAnimatorHandle);
+ Q_ASSERT(blendClipAnimator);
+
+ // TO DO: Add support for tree of blend nodes
+ // For now assumes only one
+
+ const bool canRun = blendClipAnimator->canRun();
+ m_handler->setBlendedClipAnimatorRunning(blendedClipAnimatorHandle, canRun);
+
+ // Check if blend clip can run and if so build blend tree
+ if (canRun) {
+ const ChannelMapper *mapper = m_handler->channelMapperManager()->lookupResource(blendClipAnimator->mapperId());
+ Q_ASSERT(mapper);
+ ClipBlendNode *node = m_handler->clipBlendNodeManager()->lookupNode(blendClipAnimator->blendTreeRootId());
+
+ const Qt3DCore::QNodeIdVector clipIds = node->clipIds();
+ // There must be 2 clips
+ if (clipIds.size() != 2) {
+ qWarning() << "A Blend Tree requires exactly 2 clips";
+ return;
+ }
+
+ // Retrieve Animation clips
+ const AnimationClip *clip1 = m_handler->animationClipManager()->lookupResource(clipIds.first());
+ const AnimationClip *clip2 = m_handler->animationClipManager()->lookupResource(clipIds.last());
+
+ // Build mappings for the 2 clips
+ const QVector<AnimationUtils::MappingData> mappingDataClip1 = AnimationUtils::buildPropertyMappings(m_handler, clip1, mapper);
+ const QVector<AnimationUtils::MappingData> mappingDataClip2 = AnimationUtils::buildPropertyMappings(m_handler, clip2, mapper);
+
+ // We can only blend channels that are in both clips
+ // If a channel is present in one clip and not the other, we use 100% of its value (no blending)
+ QVector<AnimationUtils::BlendingMappingData> blendingMappingData;
+ const int mappingInClip1Size = mappingDataClip1.size();
+ blendingMappingData.reserve(mappingInClip1Size);
+
+ // Find mappings that are in both vectors and build mappingData out of that
+ for (const AnimationUtils::MappingData &mappingDataInClip1 : mappingDataClip1) {
+ AnimationUtils::BlendingMappingData mappingData;
+ mappingData.channelIndicesClip1 = mappingDataInClip1.channelIndices;
+ mappingData.propertyName = mappingDataInClip1.propertyName;
+ mappingData.targetId = mappingDataInClip1.targetId;
+ mappingData.type = mappingDataInClip1.type;
+ mappingData.blendAction = AnimationUtils::BlendingMappingData::NoBlending;
+ blendingMappingData.push_back(mappingData);
+ }
+
+ for (const AnimationUtils::MappingData &mappingDataInClip2 : mappingDataClip2) {
+ bool sharedChannel = false;
+ for (int i = 0; i < mappingInClip1Size; ++i) {
+ AnimationUtils::BlendingMappingData &mappingDataInClip1 = blendingMappingData[i];
+ if ((strcmp(mappingDataInClip1.propertyName, mappingDataInClip2.propertyName) == 0) &&
+ mappingDataInClip1.targetId == mappingDataInClip2.targetId &&
+ mappingDataInClip1.type == mappingDataInClip2.type) {
+ // We have a channel shared in both clips
+ mappingDataInClip1.channelIndicesClip2 = mappingDataInClip2.channelIndices;
+ mappingDataInClip1.blendAction = AnimationUtils::BlendingMappingData::ClipBlending;
+ sharedChannel = true;
+ break;
+ }
+ }
+ if (!sharedChannel) { // We have a channel defined in only one of the clips
+ AnimationUtils::BlendingMappingData mappingData;
+ mappingData.channelIndicesClip2 = mappingDataInClip2.channelIndices;
+ mappingData.propertyName = mappingDataInClip2.propertyName;
+ mappingData.targetId = mappingDataInClip2.targetId;
+ mappingData.type = mappingDataInClip2.type;
+ mappingData.blendAction = AnimationUtils::BlendingMappingData::NoBlending;
+ blendingMappingData.push_back(mappingData);
+ }
+ }
+
+ blendClipAnimator->setMappingData(blendingMappingData);
+ }
+ }
+}
+
+} // Animation
+} // Qt3DAnimation
+
+QT_END_NAMESPACE
diff --git a/src/animation/backend/buildblendtreesjob_p.h b/src/animation/backend/buildblendtreesjob_p.h
new file mode 100644
index 000000000..cf48b10a9
--- /dev/null
+++ b/src/animation/backend/buildblendtreesjob_p.h
@@ -0,0 +1,86 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QT3DANIMATION_ANIMATION_BUILDBLENDTREESJOB_P_H
+#define QT3DANIMATION_ANIMATION_BUILDBLENDTREESJOB_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <Qt3DCore/qaspectjob.h>
+#include <Qt3DAnimation/private/handle_types_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DAnimation {
+namespace Animation {
+
+class Handler;
+
+class BuildBlendTreesJob : public Qt3DCore::QAspectJob
+{
+public:
+ BuildBlendTreesJob();
+
+ void setHandler(Handler *handler) { m_handler = handler; }
+ Handler *handler() const { return m_handler; }
+
+ void setBlendedClipAnimators(const QVector<HBlendedClipAnimator> &blendedClipAnimatorHandles);
+
+protected:
+ void run() Q_DECL_OVERRIDE;
+
+private:
+ QVector<HBlendedClipAnimator> m_blendedClipAnimatorHandles;
+ Handler *m_handler;
+};
+
+typedef QSharedPointer<BuildBlendTreesJob> BuildBlendTreesJobPtr;
+
+} // Animation
+} // Qt3DAnimation
+
+QT_END_NAMESPACE
+
+#endif // QT3DANIMATION_ANIMATION_BUILDBLENDTREESJOB_P_H
diff --git a/src/animation/backend/evaluateblendclipanimatorjob.cpp b/src/animation/backend/evaluateblendclipanimatorjob.cpp
new file mode 100644
index 000000000..e15a419b1
--- /dev/null
+++ b/src/animation/backend/evaluateblendclipanimatorjob.cpp
@@ -0,0 +1,159 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "evaluateblendclipanimatorjob_p.h"
+#include <Qt3DAnimation/private/handler_p.h>
+#include <Qt3DAnimation/private/managers_p.h>
+#include <Qt3DAnimation/private/animationlogging_p.h>
+#include <Qt3DAnimation/private/animationutils_p.h>
+#include <Qt3DAnimation/private/lerpblend_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DAnimation {
+namespace Animation {
+
+EvaluateBlendClipAnimatorJob::EvaluateBlendClipAnimatorJob()
+ : Qt3DCore::QAspectJob()
+{
+ // TO DO: Add Profiler ID
+}
+
+void EvaluateBlendClipAnimatorJob::run()
+{
+ const qint64 globalTime = m_handler->simulationTime();
+ //qDebug() << Q_FUNC_INFO << "t_global =" << globalTime;
+
+ BlendedClipAnimator *blendedClipAnimator = m_handler->blendedClipAnimatorManager()->data(m_blendClipAnimatorHandle);
+ Q_ASSERT(blendedClipAnimator);
+
+ // TO DO: Right now we are doing LERP but refactor to handle other types
+ ClipBlendNode *blendNode = m_handler->clipBlendNodeManager()->lookupNode(blendedClipAnimator->blendTreeRootId());
+ Q_ASSERT(blendNode->blendType() == ClipBlendNode::LerpBlendType);
+ LerpBlend *lerpBlendNode = static_cast<LerpBlend *>(blendNode);
+
+ const Qt3DCore::QNodeIdVector clipIds = lerpBlendNode->clipIds();
+
+ bool globalFinalFrame = false;
+
+ // Evaluate the fcurves for both clip
+ AnimationClip *clip1 = m_handler->animationClipManager()->lookupResource(clipIds.first());
+ AnimationClip *clip2 = m_handler->animationClipManager()->lookupResource(clipIds.last());
+ Q_ASSERT(clip1 && clip2);
+ bool finalFrame1 = false;
+ bool finalFrame2 = false;
+ int currentLoop = 0;
+ const QVector<float> channelResultsClip1 = AnimationUtils::evaluateAtGlobalTime(clip1,
+ globalTime,
+ blendedClipAnimator->startTime(),
+ blendedClipAnimator->loops(),
+ currentLoop,
+ finalFrame1);
+ const QVector<float> channelResultsClip2 = AnimationUtils::evaluateAtGlobalTime(clip2,
+ globalTime,
+ blendedClipAnimator->startTime(),
+ blendedClipAnimator->loops(),
+ currentLoop,
+ finalFrame2);
+ globalFinalFrame = (finalFrame1 && finalFrame2);
+
+ blendedClipAnimator->setCurrentLoop(currentLoop);
+
+ // Perform blending between the two clips
+ const float blendFactor = lerpBlendNode->blendFactor();
+
+ QVector<AnimationUtils::MappingData> blendedMappingData;
+ QVector<float> blendedValues;
+
+ // Build a combined vector of blended value
+ const QVector<AnimationUtils::BlendingMappingData> blendingMappingData = blendedClipAnimator->mappingData();
+ for (const AnimationUtils::BlendingMappingData &mapping : blendingMappingData) {
+
+ AnimationUtils::MappingData finalMapping;
+ finalMapping.type = mapping.type;
+ finalMapping.targetId = mapping.targetId;
+ finalMapping.propertyName = mapping.propertyName;
+
+ switch (mapping.blendAction) {
+ case AnimationUtils::BlendingMappingData::ClipBlending: {
+ Q_ASSERT(mapping.channelIndicesClip1.size() == mapping.channelIndicesClip2.size());
+ for (int i = 0, m = mapping.channelIndicesClip1.size(); i < m; ++i) {
+ const float value1 = channelResultsClip1.at(mapping.channelIndicesClip1[i]);
+ const float value2 = channelResultsClip2.at(mapping.channelIndicesClip2[i]);
+ const float blendedValue = ((1.0f - blendFactor) * value1) + (blendFactor * value2);
+ finalMapping.channelIndices.push_back(blendedValues.size());
+ blendedValues.push_back(blendedValue);
+ }
+ break;
+ }
+ case AnimationUtils::BlendingMappingData::NoBlending: {
+ const bool useClip1 = !mapping.channelIndicesClip1.empty();
+ const QVector<int> channelIndices = useClip1 ? mapping.channelIndicesClip1 : mapping.channelIndicesClip2;
+ const QVector<float> values = useClip1 ? channelResultsClip1 : channelResultsClip2;
+ for (int i = 0, m = channelIndices.size(); i < m; ++i) {
+ const float value = values.at(channelIndices[i]);
+ finalMapping.channelIndices.push_back(blendedValues.size());
+ blendedValues.push_back(value);
+ }
+ break;
+ }
+ default:
+ Q_UNREACHABLE();
+ break;
+ }
+
+ blendedMappingData.push_back(finalMapping);
+ }
+
+ if (globalFinalFrame)
+ blendedClipAnimator->setRunning(false);
+
+ // Prepare property changes (if finalFrame it also prepares the change for the running property for the frontend)
+ const QVector<Qt3DCore::QSceneChangePtr> changes = AnimationUtils::preparePropertyChanges(blendedClipAnimator->peerId(),
+ blendedMappingData,
+ blendedValues,
+ globalFinalFrame);
+
+ // Send the property changes
+ blendedClipAnimator->sendPropertyChanges(changes);
+
+}
+
+
+} // Animation
+} // Qt3DAnimation
+
+QT_END_NAMESPACE
diff --git a/src/animation/backend/evaluateblendclipanimatorjob_p.h b/src/animation/backend/evaluateblendclipanimatorjob_p.h
new file mode 100644
index 000000000..07db68fdc
--- /dev/null
+++ b/src/animation/backend/evaluateblendclipanimatorjob_p.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt3D module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL3$
+** 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 http://www.qt.io/terms-conditions. For further
+** information use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPLv3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or later as published by the Free
+** Software Foundation and appearing in the file LICENSE.GPL included in
+** the packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 2.0 requirements will be
+** met: http://www.gnu.org/licenses/gpl-2.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QT3DANIMATION_ANIMATION_EVALUATEBLENDCLIPANIMATORJOB_P_H
+#define QT3DANIMATION_ANIMATION_EVALUATEBLENDCLIPANIMATORJOB_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of other Qt classes. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <Qt3DCore/qaspectjob.h>
+#include <Qt3DAnimation/private/handle_types_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace Qt3DAnimation {
+namespace Animation {
+
+class Handler;
+
+class EvaluateBlendClipAnimatorJob : public Qt3DCore::QAspectJob
+{
+public:
+ EvaluateBlendClipAnimatorJob();
+
+ void setHandler(Handler *handler) { m_handler = handler; }
+ Handler *handler() const { return m_handler; }
+
+ void setBlendClipAnimator(const HBlendedClipAnimator &blendClipAnimatorHandle) { m_blendClipAnimatorHandle = blendClipAnimatorHandle; }
+ HBlendedClipAnimator blendClipAnimator() const { return m_blendClipAnimatorHandle; }
+
+protected:
+ void run() Q_DECL_OVERRIDE;
+
+private:
+ HBlendedClipAnimator m_blendClipAnimatorHandle;
+ Handler *m_handler;
+};
+
+typedef QSharedPointer<EvaluateBlendClipAnimatorJob> EvaluateBlendClipAnimatorJobPtr;
+
+} // Animation
+} // Qt3DAnimation
+
+QT_END_NAMESPACE
+
+#endif // QT3DANIMATION_ANIMATION_EVALUATEBLENDCLIPANIMATORJOB_P_H
diff --git a/src/animation/backend/handler.cpp b/src/animation/backend/handler.cpp
index 526a9c0ff..6dea5d6f3 100644
--- a/src/animation/backend/handler.cpp
+++ b/src/animation/backend/handler.cpp
@@ -39,6 +39,8 @@
#include <Qt3DAnimation/private/loadanimationclipjob_p.h>
#include <Qt3DAnimation/private/findrunningclipanimatorsjob_p.h>
#include <Qt3DAnimation/private/evaluateclipanimatorjob_p.h>
+#include <Qt3DAnimation/private/buildblendtreesjob_p.h>
+#include <Qt3DAnimation/private/evaluateblendclipanimatorjob_p.h>
#include <Qt3DAnimation/private/animationlogging_p.h>
QT_BEGIN_NAMESPACE
@@ -56,10 +58,12 @@ Handler::Handler()
, m_clipBlendNodeManager(new ClipBlendNodeManager)
, m_loadAnimationClipJob(new LoadAnimationClipJob)
, m_findRunningClipAnimatorsJob(new FindRunningClipAnimatorsJob)
+ , m_buildBlendTreesJob(new BuildBlendTreesJob)
, m_simulationTime(0)
{
m_loadAnimationClipJob->setHandler(this);
m_findRunningClipAnimatorsJob->setHandler(this);
+ m_buildBlendTreesJob->setHandler(this);
}
Handler::~Handler()
@@ -86,6 +90,12 @@ void Handler::setDirty(DirtyFlag flag, Qt3DCore::QNodeId nodeId)
m_dirtyClipAnimators.push_back(handle);
break;
}
+
+ case BlendedClipAnimatorDirty: {
+ const HBlendedClipAnimator handle = m_blendedClipAnimatorManager->lookupHandle(nodeId);
+ m_dirtyBlendedAnimators.push_back(handle);
+ break;
+ }
}
}
@@ -109,6 +119,26 @@ void Handler::setClipAnimatorRunning(const HClipAnimator &handle, bool running)
}
}
+void Handler::setBlendedClipAnimatorRunning(const HBlendedClipAnimator &handle, bool running)
+{
+ // Add clip to running set if not already present
+ if (running && !m_runningBlendedClipAnimators.contains(handle)) {
+ m_runningBlendedClipAnimators.push_back(handle);
+ BlendedClipAnimator *blendedClipAnimator = m_blendedClipAnimatorManager->data(handle);
+ if (blendedClipAnimator)
+ blendedClipAnimator->setStartTime(m_simulationTime);
+ }
+
+ // If being marked as not running, remove from set of running clips
+ if (!running) {
+ const auto it = std::find_if(m_runningBlendedClipAnimators.begin(),
+ m_runningBlendedClipAnimators.end(),
+ [handle](const HBlendedClipAnimator &h) { return h == handle; });
+ if (it != m_runningBlendedClipAnimators.end())
+ m_runningBlendedClipAnimators.erase(it);
+ }
+}
+
QVector<Qt3DCore::QAspectJobPtr> Handler::jobsToExecute(qint64 time)
{
// Store the simulation time so we can mark the start time of
@@ -140,6 +170,13 @@ QVector<Qt3DCore::QAspectJobPtr> Handler::jobsToExecute(qint64 time)
m_dirtyClipAnimators.clear();
}
+ // Rebuild blending trees if a blend tree is dirty
+ if (!m_dirtyBlendedAnimators.isEmpty()) {
+ const QVector<HBlendedClipAnimator> dirtyBlendedAnimators = std::move(m_dirtyBlendedAnimators);
+ m_buildBlendTreesJob->setBlendedClipAnimators(dirtyBlendedAnimators);
+ jobs.push_back(m_buildBlendTreesJob);
+ }
+
// TODO: Parallelise the animator evaluation and property building at a finer level
// If there are any running ClipAnimators, evaluate them for the current
@@ -170,6 +207,31 @@ QVector<Qt3DCore::QAspectJobPtr> Handler::jobsToExecute(qint64 time)
}
}
+ // BlendClipAnimator execution
+ if (!m_runningBlendedClipAnimators.isEmpty()) {
+ // Ensure we have a job per clip animator
+ const int oldSize = m_evaluateBlendClipAnimatorJobs.size();
+ const int newSize = m_runningBlendedClipAnimators.size();
+ if (oldSize < newSize) {
+ m_evaluateBlendClipAnimatorJobs.resize(newSize);
+ for (int i = oldSize; i < newSize; ++i) {
+ m_evaluateBlendClipAnimatorJobs[i].reset(new EvaluateBlendClipAnimatorJob());
+ m_evaluateBlendClipAnimatorJobs[i]->setHandler(this);
+ }
+ }
+
+ // Set each job up with an animator to process and set dependencies
+ for (int i = 0; i < newSize; ++i) {
+ m_evaluateBlendClipAnimatorJobs[i]->setBlendClipAnimator(m_runningBlendedClipAnimators[i]);
+ m_evaluateBlendClipAnimatorJobs[i]->removeDependency(QWeakPointer<Qt3DCore::QAspectJob>());
+ if (jobs.contains(m_loadAnimationClipJob))
+ m_evaluateBlendClipAnimatorJobs[i]->addDependency(m_loadAnimationClipJob);
+ if (jobs.contains(m_buildBlendTreesJob))
+ m_evaluateBlendClipAnimatorJobs[i]->addDependency(m_buildBlendTreesJob);
+ jobs.push_back(m_evaluateBlendClipAnimatorJobs[i]);
+ }
+ }
+
return jobs;
}
diff --git a/src/animation/backend/handler_p.h b/src/animation/backend/handler_p.h
index a2f5daa8f..3abaae215 100644
--- a/src/animation/backend/handler_p.h
+++ b/src/animation/backend/handler_p.h
@@ -50,6 +50,8 @@
#include <QtGlobal>
#include <Qt3DAnimation/private/handle_types_p.h>
+#include <Qt3DAnimation/private/buildblendtreesjob_p.h>
+#include <Qt3DAnimation/private/evaluateblendclipanimatorjob_p.h>
#include <Qt3DCore/qaspectjob.h>
#include <Qt3DCore/qnodeid.h>
#include <QtCore/qscopedpointer.h>
@@ -90,7 +92,8 @@ public:
enum DirtyFlag {
AnimationClipDirty,
ChannelMappingsDirty,
- ClipAnimatorDirty
+ ClipAnimatorDirty,
+ BlendedClipAnimatorDirty
};
qint64 simulationTime() const { return m_simulationTime; }
@@ -100,6 +103,9 @@ public:
void setClipAnimatorRunning(const HClipAnimator &handle, bool running);
QVector<HClipAnimator> runningClipAnimators() const { return m_runningClipAnimators; }
+ void setBlendedClipAnimatorRunning(const HBlendedClipAnimator &handle, bool running);
+ QVector<HBlendedClipAnimator> runningBlenndedClipAnimators() const { return m_runningBlendedClipAnimators; }
+
AnimationClipManager *animationClipManager() const Q_DECL_NOTHROW { return m_animationClipManager.data(); }
ClipAnimatorManager *clipAnimatorManager() const Q_DECL_NOTHROW { return m_clipAnimatorManager.data(); }
BlendedClipAnimatorManager *blendedClipAnimatorManager() const Q_DECL_NOTHROW { return m_blendedClipAnimatorManager.data(); }
@@ -122,12 +128,16 @@ private:
QVector<HAnimationClip> m_dirtyAnimationClips;
QVector<HChannelMapper> m_dirtyChannelMappers;
QVector<HClipAnimator> m_dirtyClipAnimators;
+ QVector<HBlendedClipAnimator> m_dirtyBlendedAnimators;
QVector<HClipAnimator> m_runningClipAnimators;
+ QVector<HBlendedClipAnimator> m_runningBlendedClipAnimators;
QSharedPointer<LoadAnimationClipJob> m_loadAnimationClipJob;
QSharedPointer<FindRunningClipAnimatorsJob> m_findRunningClipAnimatorsJob;
QVector<QSharedPointer<EvaluateClipAnimatorJob>> m_evaluateClipAnimatorJobs;
+ QVector<EvaluateBlendClipAnimatorJobPtr> m_evaluateBlendClipAnimatorJobs;
+ BuildBlendTreesJobPtr m_buildBlendTreesJob;
qint64 m_simulationTime;
diff --git a/src/animation/frontend/qblendedclipanimator.cpp b/src/animation/frontend/qblendedclipanimator.cpp
index 1e89f37a2..8e0897b5c 100644
--- a/src/animation/frontend/qblendedclipanimator.cpp
+++ b/src/animation/frontend/qblendedclipanimator.cpp
@@ -50,6 +50,7 @@ QBlendedClipAnimatorPrivate::QBlendedClipAnimatorPrivate()
, m_blendTreeRoot(nullptr)
, m_mapper(nullptr)
, m_running(false)
+ , m_loops(1)
{
}
@@ -85,6 +86,12 @@ QChannelMapper *QBlendedClipAnimator::channelMapper() const
return d->m_mapper;
}
+int QBlendedClipAnimator::loops() const
+{
+ Q_D(const QBlendedClipAnimator);
+ return d->m_loops;
+}
+
void QBlendedClipAnimator::setBlendTree(QAbstractClipBlendNode *blendTree)
{
Q_D(QBlendedClipAnimator);
@@ -115,6 +122,17 @@ void QBlendedClipAnimator::setRunning(bool running)
emit runningChanged(running);
}
+void QBlendedClipAnimator::setLoops(int loops)
+{
+ Q_D(QBlendedClipAnimator);
+ if (d->m_loops == loops)
+ return;
+
+ d->m_loops = loops;
+ emit loopsChanged(loops);
+}
+
+
void QBlendedClipAnimator::setChannelMapper(QChannelMapper *mapping)
{
Q_D(QBlendedClipAnimator);
@@ -141,6 +159,7 @@ Qt3DCore::QNodeCreatedChangeBasePtr QBlendedClipAnimator::createNodeCreationChan
data.blendTreeRootId = Qt3DCore::qIdForNode(d->m_blendTreeRoot);
data.mapperId = Qt3DCore::qIdForNode(d->m_mapper);
data.running = d->m_running;
+ data.loops = d->m_loops;
return creationChange;
}
diff --git a/src/animation/frontend/qblendedclipanimator.h b/src/animation/frontend/qblendedclipanimator.h
index 0b520e042..be1189f4f 100644
--- a/src/animation/frontend/qblendedclipanimator.h
+++ b/src/animation/frontend/qblendedclipanimator.h
@@ -56,6 +56,7 @@ class QT3DANIMATIONSHARED_EXPORT QBlendedClipAnimator : public Qt3DCore::QCompon
Q_OBJECT
Q_PROPERTY(Qt3DAnimation::QAbstractClipBlendNode *blendTree READ blendTree WRITE setBlendTree NOTIFY blendTreeChanged)
Q_PROPERTY(Qt3DAnimation::QChannelMapper *channelMapper READ channelMapper WRITE setChannelMapper NOTIFY channelMapperChanged)
+ Q_PROPERTY(int loops READ loops WRITE setLoops NOTIFY loopsChanged)
Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged)
public:
@@ -65,16 +66,19 @@ public:
bool isRunning() const;
Qt3DAnimation::QChannelMapper *channelMapper() const;
QAbstractClipBlendNode *blendTree() const;
+ int loops() const;
public Q_SLOTS:
void setRunning(bool running);
void setChannelMapper(QChannelMapper *channelMapper);
void setBlendTree(QAbstractClipBlendNode * blendTree);
+ void setLoops(int loops);
Q_SIGNALS:
void blendTreeChanged(QAbstractClipBlendNode * blendTree);
void runningChanged(bool running);
void channelMapperChanged(QChannelMapper *channelMapper);
+ void loopsChanged(int loops);
protected:
QBlendedClipAnimator(QBlendedClipAnimatorPrivate &dd, Qt3DCore::QNode *parent = nullptr);
diff --git a/src/animation/frontend/qblendedclipanimator_p.h b/src/animation/frontend/qblendedclipanimator_p.h
index 6d29a262b..47a8bbfd5 100644
--- a/src/animation/frontend/qblendedclipanimator_p.h
+++ b/src/animation/frontend/qblendedclipanimator_p.h
@@ -69,6 +69,7 @@ public:
QAbstractClipBlendNode *m_blendTreeRoot;
QChannelMapper *m_mapper;
bool m_running;
+ int m_loops;
};
struct QBlendedClipAnimatorData
@@ -76,6 +77,7 @@ struct QBlendedClipAnimatorData
Qt3DCore::QNodeId blendTreeRootId;
Qt3DCore::QNodeId mapperId;
bool running;
+ int loops;
};
} // namespace Qt3DAnimation
diff --git a/tests/auto/animation/blendedclipanimator/tst_blendedclipanimator.cpp b/tests/auto/animation/blendedclipanimator/tst_blendedclipanimator.cpp
index 08e8e8bd7..c53f6ec62 100644
--- a/tests/auto/animation/blendedclipanimator/tst_blendedclipanimator.cpp
+++ b/tests/auto/animation/blendedclipanimator/tst_blendedclipanimator.cpp
@@ -63,6 +63,11 @@ private Q_SLOTS:
QCOMPARE(backendBlendedClipAnimator.blendTreeRootId(), Qt3DCore::QNodeId());
QCOMPARE(backendBlendedClipAnimator.mapperId(), Qt3DCore::QNodeId());
QCOMPARE(backendBlendedClipAnimator.isRunning(), false);
+ QCOMPARE(backendBlendedClipAnimator.startTime(), 0);
+ QCOMPARE(backendBlendedClipAnimator.currentLoop(), 0);
+ QCOMPARE(backendBlendedClipAnimator.mappingData().size(), 0);
+ QCOMPARE(backendBlendedClipAnimator.loops(), 1);
+
}
void checkCleanupState()
@@ -77,7 +82,10 @@ private Q_SLOTS:
backendBlendedClipAnimator.setBlendTreeRootId(Qt3DCore::QNodeId::createId());
backendBlendedClipAnimator.setMapperId(Qt3DCore::QNodeId::createId());
backendBlendedClipAnimator.setRunning(true);
-
+ backendBlendedClipAnimator.setStartTime(28);
+ QVector<Qt3DAnimation::Animation::AnimationUtils::BlendingMappingData> mappingData;
+ mappingData.resize(5);
+ backendBlendedClipAnimator.setMappingData(mappingData);
backendBlendedClipAnimator.cleanup();
// THEN
@@ -85,6 +93,10 @@ private Q_SLOTS:
QCOMPARE(backendBlendedClipAnimator.blendTreeRootId(), Qt3DCore::QNodeId());
QCOMPARE(backendBlendedClipAnimator.mapperId(), Qt3DCore::QNodeId());
QCOMPARE(backendBlendedClipAnimator.isRunning(), false);
+ QCOMPARE(backendBlendedClipAnimator.startTime(), 0);
+ QCOMPARE(backendBlendedClipAnimator.currentLoop(), 0);
+ QCOMPARE(backendBlendedClipAnimator.mappingData().size(), 0);
+ QCOMPARE(backendBlendedClipAnimator.loops(), 1);
}
void checkInitializeFromPeer()
@@ -96,6 +108,7 @@ private Q_SLOTS:
blendedClipAnimator.setRunning(true);
blendedClipAnimator.setBlendTree(&blendTree);
blendedClipAnimator.setChannelMapper(&mapper);
+ blendedClipAnimator.setLoops(10);
{
// WHEN
@@ -111,6 +124,7 @@ private Q_SLOTS:
QCOMPARE(backendBlendedClipAnimator.blendTreeRootId(), blendTree.id());
QCOMPARE(backendBlendedClipAnimator.mapperId(), mapper.id());
QCOMPARE(backendBlendedClipAnimator.isRunning(), true);
+ QCOMPARE(backendBlendedClipAnimator.loops(), 10);
}
{
// WHEN
@@ -178,6 +192,17 @@ private Q_SLOTS:
// THEN
QCOMPARE(backendBlendedClipAnimator.isRunning(), newValue);
}
+ {
+ // WHEN
+ const int newValue = 883;
+ const auto change = Qt3DCore::QPropertyUpdatedChangePtr::create(Qt3DCore::QNodeId());
+ change->setPropertyName("loops");
+ change->setValue(QVariant::fromValue(newValue));
+ backendBlendedClipAnimator.sceneChangeEvent(change);
+
+ // THEN
+ QCOMPARE(backendBlendedClipAnimator.loops(), newValue);
+ }
}
};
diff --git a/tests/auto/animation/qblendedclipanimator/tst_qblendedclipanimator.cpp b/tests/auto/animation/qblendedclipanimator/tst_qblendedclipanimator.cpp
index 0fe3b454c..42997d8aa 100644
--- a/tests/auto/animation/qblendedclipanimator/tst_qblendedclipanimator.cpp
+++ b/tests/auto/animation/qblendedclipanimator/tst_qblendedclipanimator.cpp
@@ -62,6 +62,7 @@ private Q_SLOTS:
QVERIFY(blendedClipAnimator.blendTree() == nullptr);
QVERIFY(blendedClipAnimator.channelMapper() == nullptr);
QCOMPARE(blendedClipAnimator.isRunning(), false);
+ QCOMPARE(blendedClipAnimator.loops(), 1);
}
void checkPropertyChanges()
@@ -126,6 +127,26 @@ private Q_SLOTS:
QCOMPARE(blendedClipAnimator.isRunning(), newValue);
QCOMPARE(spy.count(), 0);
}
+
+ {
+ // WHEN
+ QSignalSpy spy(&blendedClipAnimator, SIGNAL(loopsChanged(int)));
+ const int newValue = 5;
+ blendedClipAnimator.setLoops(newValue);
+
+ // THEN
+ QVERIFY(spy.isValid());
+ QCOMPARE(blendedClipAnimator.loops(), newValue);
+ QCOMPARE(spy.count(), 1);
+
+ // WHEN
+ spy.clear();
+ blendedClipAnimator.setLoops(newValue);
+
+ // THEN
+ QCOMPARE(blendedClipAnimator.loops(), newValue);
+ QCOMPARE(spy.count(), 0);
+ }
}
void checkCreationData()
@@ -161,6 +182,7 @@ private Q_SLOTS:
QCOMPARE(blendedClipAnimator.isEnabled(), true);
QCOMPARE(blendedClipAnimator.isEnabled(), creationChangeData->isNodeEnabled());
QCOMPARE(blendedClipAnimator.metaObject(), creationChangeData->metaObject());
+ QCOMPARE(blendedClipAnimator.loops(), cloneData.loops);
}
// WHEN
@@ -322,6 +344,39 @@ private Q_SLOTS:
}
}
+
+ void checkLoopsUpdate()
+ {
+ // GIVEN
+ TestArbiter arbiter;
+ Qt3DAnimation::QBlendedClipAnimator blendedClipAnimator;
+ arbiter.setArbiterOnNode(&blendedClipAnimator);
+
+ {
+ // WHEN
+ blendedClipAnimator.setLoops(1584);
+ QCoreApplication::processEvents();
+
+ // THEN
+ QCOMPARE(arbiter.events.size(), 1);
+ auto change = arbiter.events.first().staticCast<Qt3DCore::QPropertyUpdatedChange>();
+ QCOMPARE(change->propertyName(), "loops");
+ QCOMPARE(change->value().value<int>(), blendedClipAnimator.loops());
+ QCOMPARE(change->type(), Qt3DCore::PropertyUpdated);
+
+ arbiter.events.clear();
+ }
+
+ {
+ // WHEN
+ blendedClipAnimator.setLoops(1584);
+ QCoreApplication::processEvents();
+
+ // THEN
+ QCOMPARE(arbiter.events.size(), 0);
+ }
+
+ }
};
QTEST_MAIN(tst_QBlendedClipAnimator)
diff --git a/tests/manual/animation-keyframe-simple/main.qml b/tests/manual/animation-keyframe-simple/main.qml
index a16de3f17..8546d8b34 100644
--- a/tests/manual/animation-keyframe-simple/main.qml
+++ b/tests/manual/animation-keyframe-simple/main.qml
@@ -84,6 +84,7 @@ DefaultSceneEntity {
},
BlendedClipAnimator {
id: blendedAnimator
+ loops: 2
onRunningChanged: console.log("running = " + running)