1/****************************************************************************
2**
3** Copyright (C) 2017 Klaralvdalens Datakonsult AB (KDAB).
4** Contact: http://www.qt-project.org/legal
5**
6** This file is part of the Qt3D module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL3$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see http://www.qt.io/terms-conditions. For further
15** information use the contact form at http://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPLv3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or later as published by the Free
28** Software Foundation and appearing in the file LICENSE.GPL included in
29** the packaging of this file. Please review the following information to
30** ensure the GNU General Public License version 2.0 requirements will be
31** met: http://www.gnu.org/licenses/gpl-2.0.html.
32**
33** $QT_END_LICENSE$
34**
35****************************************************************************/
36
37#include "handler_p.h"
38#include <Qt3DAnimation/private/managers_p.h>
39#include <Qt3DAnimation/private/loadanimationclipjob_p.h>
40#include <Qt3DAnimation/private/findrunningclipanimatorsjob_p.h>
41#include <Qt3DAnimation/private/evaluateclipanimatorjob_p.h>
42#include <Qt3DAnimation/private/buildblendtreesjob_p.h>
43#include <Qt3DAnimation/private/evaluateblendclipanimatorjob_p.h>
44#include <Qt3DAnimation/private/animationlogging_p.h>
45#include <Qt3DAnimation/private/buildblendtreesjob_p.h>
46#include <Qt3DAnimation/private/evaluateblendclipanimatorjob_p.h>
47#include <Qt3DCore/private/qaspectjob_p.h>
48
49QT_BEGIN_NAMESPACE
50
51namespace Qt3DAnimation {
52namespace Animation {
53
54Handler::Handler()
55 : m_animationClipLoaderManager(new AnimationClipLoaderManager)
56 , m_clockManager(new ClockManager)
57 , m_clipAnimatorManager(new ClipAnimatorManager)
58 , m_blendedClipAnimatorManager(new BlendedClipAnimatorManager)
59 , m_channelMappingManager(new ChannelMappingManager)
60 , m_channelMapperManager(new ChannelMapperManager)
61 , m_clipBlendNodeManager(new ClipBlendNodeManager)
62 , m_skeletonManager(new SkeletonManager)
63 , m_loadAnimationClipJob(new LoadAnimationClipJob)
64 , m_findRunningClipAnimatorsJob(new FindRunningClipAnimatorsJob)
65 , m_buildBlendTreesJob(new BuildBlendTreesJob)
66 , m_simulationTime(0)
67{
68 m_loadAnimationClipJob->setHandler(this);
69 m_findRunningClipAnimatorsJob->setHandler(this);
70 m_buildBlendTreesJob->setHandler(this);
71}
72
73Handler::~Handler()
74{
75}
76
77void Handler::setDirty(DirtyFlag flag, Qt3DCore::QNodeId nodeId)
78{
79 switch (flag) {
80 case AnimationClipDirty: {
81 QMutexLocker lock(&m_mutex);
82 const auto handle = m_animationClipLoaderManager->lookupHandle(id: nodeId);
83 if (!m_dirtyAnimationClips.contains(t: handle))
84 m_dirtyAnimationClips.push_back(t: handle);
85 break;
86 }
87
88 case ChannelMappingsDirty: {
89 break;
90 }
91
92 case ClipAnimatorDirty: {
93 QMutexLocker lock(&m_mutex);
94 const auto handle = m_clipAnimatorManager->lookupHandle(id: nodeId);
95 if (!m_dirtyClipAnimators.contains(t: handle))
96 m_dirtyClipAnimators.push_back(t: handle);
97 break;
98 }
99
100 case BlendedClipAnimatorDirty: {
101 QMutexLocker lock(&m_mutex);
102 const HBlendedClipAnimator handle = m_blendedClipAnimatorManager->lookupHandle(id: nodeId);
103 if (!m_dirtyBlendedAnimators.contains(t: handle))
104 m_dirtyBlendedAnimators.push_back(t: handle);
105 break;
106 }
107 }
108}
109
110void Handler::setClipAnimatorRunning(const HClipAnimator &handle, bool running)
111{
112 // Add clip to running set if not already present
113 if (running && !m_runningClipAnimators.contains(t: handle)) {
114 m_runningClipAnimators.push_back(t: handle);
115 ClipAnimator *clipAnimator = m_clipAnimatorManager->data(handle);
116 if (clipAnimator)
117 clipAnimator->setStartTime(m_simulationTime);
118 }
119
120 // If being marked as not running, remove from set of running clips
121 if (!running) {
122 m_runningClipAnimators.removeAll(t: handle);
123 }
124}
125
126void Handler::setBlendedClipAnimatorRunning(const HBlendedClipAnimator &handle, bool running)
127{
128 // Add clip to running set if not already present
129 if (running && !m_runningBlendedClipAnimators.contains(t: handle)) {
130 m_runningBlendedClipAnimators.push_back(t: handle);
131 BlendedClipAnimator *blendedClipAnimator = m_blendedClipAnimatorManager->data(handle);
132 if (blendedClipAnimator)
133 blendedClipAnimator->setStartTime(m_simulationTime);
134 }
135
136 // If being marked as not running, remove from set of running clips
137 if (!running) {
138 const auto it = std::find_if(first: m_runningBlendedClipAnimators.begin(),
139 last: m_runningBlendedClipAnimators.end(),
140 pred: [handle](const HBlendedClipAnimator &h) { return h == handle; });
141 if (it != m_runningBlendedClipAnimators.end())
142 m_runningBlendedClipAnimators.erase(pos: it);
143 }
144}
145
146// The vectors may get outdated when the application removes/deletes an
147// animator component in the meantime. Recognize this. This should be
148// relatively infrequent so in most cases the vectors will not change at all.
149void Handler::cleanupHandleList(QVector<HAnimationClip> *clips)
150{
151 for (auto it = clips->begin(); it != clips->end(); ) {
152 if (!m_animationClipLoaderManager->data(handle: *it))
153 it = clips->erase(pos: it);
154 else
155 ++it;
156 }
157}
158
159void Handler::cleanupHandleList(QVector<HClipAnimator> *animators)
160{
161 for (auto it = animators->begin(); it != animators->end(); ) {
162 if (!m_clipAnimatorManager->data(handle: *it))
163 it = animators->erase(pos: it);
164 else
165 ++it;
166 }
167}
168
169void Handler::cleanupHandleList(QVector<HBlendedClipAnimator> *animators)
170{
171 for (auto it = animators->begin(); it != animators->end(); ) {
172 if (!m_blendedClipAnimatorManager->data(handle: *it))
173 it = animators->erase(pos: it);
174 else
175 ++it;
176 }
177}
178
179QVector<Qt3DCore::QAspectJobPtr> Handler::jobsToExecute(qint64 time)
180{
181 // Store the simulation time so we can mark the start time of
182 // animators which will allow us to calculate the local time of
183 // animation clips.
184 m_simulationTime = time;
185
186 QVector<Qt3DCore::QAspectJobPtr> jobs;
187
188 QMutexLocker lock(&m_mutex);
189
190 // If there are any dirty animation clips that need loading,
191 // queue up a job for them
192 const bool hasLoadAnimationClipJob = !m_dirtyAnimationClips.isEmpty();
193 if (hasLoadAnimationClipJob) {
194 qCDebug(HandlerLogic) << "Added LoadAnimationClipJob";
195 cleanupHandleList(clips: &m_dirtyAnimationClips);
196 m_loadAnimationClipJob->addDirtyAnimationClips(animationClipHandles: m_dirtyAnimationClips);
197 jobs.push_back(t: m_loadAnimationClipJob);
198 m_dirtyAnimationClips.clear();
199 }
200
201 // If there are dirty clip animators, find the set that are able to
202 // run. I.e. are marked as running and have animation clips and
203 // channel mappings
204
205 const bool hasFindRunningClipAnimatorsJob = !m_dirtyClipAnimators.isEmpty();
206 if (hasFindRunningClipAnimatorsJob) {
207 qCDebug(HandlerLogic) << "Added FindRunningClipAnimatorsJob";
208 cleanupHandleList(animators: &m_dirtyClipAnimators);
209 m_findRunningClipAnimatorsJob->setDirtyClipAnimators(m_dirtyClipAnimators);
210 // Only set the dependency once
211 if (Q_UNLIKELY(m_findRunningClipAnimatorsJob->dependencies().empty()))
212 m_findRunningClipAnimatorsJob->addDependency(dependency: m_loadAnimationClipJob);
213 jobs.push_back(t: m_findRunningClipAnimatorsJob);
214 if (hasLoadAnimationClipJob)
215 m_dirtyClipAnimators.clear();
216 }
217
218 // Rebuild blending trees if a blend tree is dirty
219 const bool hasBuildBlendTreesJob = !m_dirtyBlendedAnimators.isEmpty();
220 if (hasBuildBlendTreesJob) {
221 const QVector<HBlendedClipAnimator> dirtyBlendedAnimators = std::move(m_dirtyBlendedAnimators);
222 m_buildBlendTreesJob->setBlendedClipAnimators(dirtyBlendedAnimators);
223 jobs.push_back(t: m_buildBlendTreesJob);
224 }
225
226 // TODO: Parallelise the animator evaluation and property building at a finer level
227
228 // If there are any running ClipAnimators, evaluate them for the current
229 // time and send property changes
230 cleanupHandleList(animators: &m_runningClipAnimators);
231 if (!m_runningClipAnimators.isEmpty()) {
232 qCDebug(HandlerLogic) << "Added EvaluateClipAnimatorJobs";
233
234 // Ensure we have a job per clip animator
235 const int oldSize = m_evaluateClipAnimatorJobs.size();
236 const int newSize = m_runningClipAnimators.size();
237 if (oldSize < newSize) {
238 m_evaluateClipAnimatorJobs.resize(asize: newSize);
239 for (int i = oldSize; i < newSize; ++i) {
240 m_evaluateClipAnimatorJobs[i] = QSharedPointer<EvaluateClipAnimatorJob>::create();
241 m_evaluateClipAnimatorJobs[i]->setHandler(this);
242 }
243 }
244
245 // Set each job up with an animator to process and set dependencies
246 for (int i = 0; i < newSize; ++i) {
247 m_evaluateClipAnimatorJobs[i]->setClipAnimator(m_runningClipAnimators[i]);
248 Qt3DCore::QAspectJobPrivate::get(job: m_evaluateClipAnimatorJobs[i].data())->clearDependencies();
249 if (hasLoadAnimationClipJob)
250 m_evaluateClipAnimatorJobs[i]->addDependency(dependency: m_loadAnimationClipJob);
251 if (hasFindRunningClipAnimatorsJob)
252 m_evaluateClipAnimatorJobs[i]->addDependency(dependency: m_findRunningClipAnimatorsJob);
253 jobs.push_back(t: m_evaluateClipAnimatorJobs[i]);
254 }
255 }
256
257 // BlendClipAnimator execution
258 cleanupHandleList(animators: &m_runningBlendedClipAnimators);
259 if (!m_runningBlendedClipAnimators.isEmpty()) {
260 // Ensure we have a job per clip animator
261 const int oldSize = m_evaluateBlendClipAnimatorJobs.size();
262 const int newSize = m_runningBlendedClipAnimators.size();
263 if (oldSize < newSize) {
264 m_evaluateBlendClipAnimatorJobs.resize(asize: newSize);
265 for (int i = oldSize; i < newSize; ++i) {
266 m_evaluateBlendClipAnimatorJobs[i] = QSharedPointer<EvaluateBlendClipAnimatorJob>::create();
267 m_evaluateBlendClipAnimatorJobs[i]->setHandler(this);
268 }
269 }
270
271 // Set each job up with an animator to process and set dependencies
272 for (int i = 0; i < newSize; ++i) {
273 m_evaluateBlendClipAnimatorJobs[i]->setBlendClipAnimator(m_runningBlendedClipAnimators[i]);
274 Qt3DCore::QAspectJobPrivate::get(job: m_evaluateBlendClipAnimatorJobs[i].data())->clearDependencies();
275 if (hasLoadAnimationClipJob)
276 m_evaluateBlendClipAnimatorJobs[i]->addDependency(dependency: m_loadAnimationClipJob);
277 if (hasBuildBlendTreesJob)
278 m_evaluateBlendClipAnimatorJobs[i]->addDependency(dependency: m_buildBlendTreesJob);
279 jobs.push_back(t: m_evaluateBlendClipAnimatorJobs[i]);
280 }
281 }
282
283 return jobs;
284}
285
286} // namespace Animation
287} // namespace Qt3DAnimation
288
289QT_END_NAMESPACE
290

source code of qt3d/src/animation/backend/handler.cpp