diff options
author | Paul Lemire <paul.lemire@kdab.com> | 2017-01-29 09:13:56 +0100 |
---|---|---|
committer | Sean Harmer <sean.harmer@kdab.com> | 2017-01-29 18:56:11 +0000 |
commit | 89766b24b5f071eaf390eadce6ba635d423201e1 (patch) | |
tree | b231e02518c4dce3f4e73eafaa5e4a5a242b3e5d | |
parent | d555d63084ac13866a78772b43b4ac6b6d9bed9b (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.h | 15 | ||||
-rw-r--r-- | src/animation/backend/backend.pri | 8 | ||||
-rw-r--r-- | src/animation/backend/blendedclipanimator.cpp | 31 | ||||
-rw-r--r-- | src/animation/backend/blendedclipanimator_p.h | 22 | ||||
-rw-r--r-- | src/animation/backend/buildblendtreesjob.cpp | 144 | ||||
-rw-r--r-- | src/animation/backend/buildblendtreesjob_p.h | 86 | ||||
-rw-r--r-- | src/animation/backend/evaluateblendclipanimatorjob.cpp | 159 | ||||
-rw-r--r-- | src/animation/backend/evaluateblendclipanimatorjob_p.h | 87 | ||||
-rw-r--r-- | src/animation/backend/handler.cpp | 62 | ||||
-rw-r--r-- | src/animation/backend/handler_p.h | 12 | ||||
-rw-r--r-- | src/animation/frontend/qblendedclipanimator.cpp | 19 | ||||
-rw-r--r-- | src/animation/frontend/qblendedclipanimator.h | 4 | ||||
-rw-r--r-- | src/animation/frontend/qblendedclipanimator_p.h | 2 | ||||
-rw-r--r-- | tests/auto/animation/blendedclipanimator/tst_blendedclipanimator.cpp | 27 | ||||
-rw-r--r-- | tests/auto/animation/qblendedclipanimator/tst_qblendedclipanimator.cpp | 55 | ||||
-rw-r--r-- | tests/manual/animation-keyframe-simple/main.qml | 1 |
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 ¤tLoop); 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) |