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 | |
49 | QT_BEGIN_NAMESPACE |
50 | |
51 | namespace Qt3DAnimation { |
52 | namespace Animation { |
53 | |
54 | Handler::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 | |
73 | Handler::~Handler() |
74 | { |
75 | } |
76 | |
77 | void 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 | |
110 | void 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 | |
126 | void 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. |
149 | void 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 | |
159 | void 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 | |
169 | void 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 | |
179 | QVector<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 | |
289 | QT_END_NAMESPACE |
290 | |