| 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 | |