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 "animationutils_p.h" |
38 | #include <Qt3DAnimation/private/handler_p.h> |
39 | #include <Qt3DAnimation/private/managers_p.h> |
40 | #include <Qt3DAnimation/private/clipblendnode_p.h> |
41 | #include <Qt3DAnimation/private/clipblendnodevisitor_p.h> |
42 | #include <Qt3DAnimation/private/clipblendvalue_p.h> |
43 | #include <QtGui/qvector2d.h> |
44 | #include <QtGui/qvector3d.h> |
45 | #include <QtGui/qvector4d.h> |
46 | #include <QtGui/qquaternion.h> |
47 | #include <QtGui/qcolor.h> |
48 | #include <QtCore/qvariant.h> |
49 | #include <QtCore/qvarlengtharray.h> |
50 | #include <Qt3DAnimation/private/animationlogging_p.h> |
51 | |
52 | #include <numeric> |
53 | |
54 | QT_BEGIN_NAMESPACE |
55 | |
56 | namespace { |
57 | const auto slerpThreshold = 0.01f; |
58 | } |
59 | |
60 | namespace Qt3DAnimation { |
61 | namespace Animation { |
62 | |
63 | inline QVector<float> valueToVector(const QVector3D &value) |
64 | { |
65 | return { value.x(), value.y(), value.z() }; |
66 | } |
67 | |
68 | inline QVector<float> valueToVector(const QQuaternion &value) |
69 | { |
70 | return { value.scalar(), value.x(), value.y(), value.z() }; |
71 | } |
72 | |
73 | ClipEvaluationData evaluationDataForClip(AnimationClip *clip, |
74 | const AnimatorEvaluationData &animatorData) |
75 | { |
76 | // global time values expected in seconds |
77 | ClipEvaluationData result; |
78 | result.currentLoop = animatorData.currentLoop; |
79 | result.localTime = localTimeFromElapsedTime(t_current_local: animatorData.currentTime, t_elapsed_global: animatorData.elapsedTime, |
80 | playbackRate: animatorData.playbackRate, duration: clip->duration(), |
81 | loopCount: animatorData.loopCount, currentLoop&: result.currentLoop); |
82 | result.isFinalFrame = isFinalFrame(localTime: result.localTime, duration: clip->duration(), |
83 | currentLoop: result.currentLoop, loopCount: animatorData.loopCount, |
84 | playbackRate: animatorData.playbackRate); |
85 | const bool hasNormalizedTime = isValidNormalizedTime(t: animatorData.normalizedLocalTime); |
86 | result.normalizedLocalTime = hasNormalizedTime ? animatorData.normalizedLocalTime |
87 | : result.localTime / clip->duration(); |
88 | return result; |
89 | } |
90 | |
91 | double localTimeFromElapsedTime(double t_current_local, |
92 | double t_elapsed_global, |
93 | double playbackRate, |
94 | double duration, |
95 | int loopCount, |
96 | int ¤tLoop) |
97 | { |
98 | // Calculate the new local time. |
99 | // playhead + rate * dt |
100 | // where playhead is completed loops * duration + current loop local time |
101 | double t_local = currentLoop * duration + t_current_local + playbackRate * t_elapsed_global; |
102 | double loopNumber = 0; |
103 | if (loopCount == 1) { |
104 | t_local = qBound(min: 0.0, val: t_local, max: duration); |
105 | } else if (loopCount < 0) { |
106 | // Loops forever |
107 | (void) std::modf(x: t_local / duration, iptr: &loopNumber); |
108 | t_local = std::fmod(x: t_local, y: duration); |
109 | } else { |
110 | // N loops |
111 | t_local = qBound(min: 0.0, val: t_local, max: double(loopCount) * duration); |
112 | (void) std::modf(x: t_local / duration, iptr: &loopNumber); |
113 | t_local = std::fmod(x: t_local, y: duration); |
114 | |
115 | // Ensure we clamp to end of final loop |
116 | |
117 | if (int(loopNumber) == loopCount || int(loopNumber) < 0) { |
118 | loopNumber = loopCount - 1; |
119 | t_local = playbackRate >= 0.0 ? duration : 0.0; |
120 | } |
121 | } |
122 | |
123 | qCDebug(Jobs) << "current loop =" << loopNumber |
124 | << "t =" << t_local |
125 | << "duration =" << duration; |
126 | |
127 | currentLoop = int(loopNumber); |
128 | |
129 | return t_local; |
130 | } |
131 | |
132 | double phaseFromElapsedTime(double t_current_local, |
133 | double t_elapsed_global, |
134 | double playbackRate, |
135 | double duration, |
136 | int loopCount, |
137 | int ¤tLoop) |
138 | { |
139 | const double t_local = localTimeFromElapsedTime(t_current_local, t_elapsed_global, playbackRate, |
140 | duration, loopCount, currentLoop); |
141 | return t_local / duration; |
142 | } |
143 | |
144 | /*! |
145 | \internal |
146 | |
147 | Calculates the indices required to map from the component ordering within the |
148 | provided \a channel, into the standard channel orderings expected by Qt types. |
149 | |
150 | For example, given a channel representing a rotation with the components ordered |
151 | as X, Y, Z, Y, this function will return the indices [3, 0, 1, 2] which can then |
152 | later be used as part of the format vector in the formatClipResults() function to |
153 | remap the channels into the standard W, X, Y, Z order required by QQuaternion. |
154 | */ |
155 | ComponentIndices channelComponentsToIndices(const Channel &channel, |
156 | int dataType, |
157 | int expectedComponentCount, |
158 | int offset) |
159 | { |
160 | #if defined Q_COMPILER_UNIFORM_INIT |
161 | static const QVector<char> standardSuffixes = { 'X', 'Y', 'Z', 'W' }; |
162 | static const QVector<char> quaternionSuffixes = { 'W', 'X', 'Y', 'Z' }; |
163 | static const QVector<char> colorSuffixesRGB = { 'R', 'G', 'B' }; |
164 | static const QVector<char> colorSuffixesRGBA = { 'R', 'G', 'B', 'A' }; |
165 | #else |
166 | static const QVector<char> standardSuffixes = (QVector<char>() << 'X' << 'Y' << 'Z' << 'W'); |
167 | static const QVector<char> quaternionSuffixes = (QVector<char>() << 'W' << 'X' << 'Y' << 'Z'); |
168 | static const QVector<char> colorSuffixesRGB = (QVector<char>() << 'R' << 'G' << 'B'); |
169 | static const QVector<char> colorSuffixesRGBA = (QVector<char>() << 'R' << 'G' << 'B' << 'A'); |
170 | #endif |
171 | |
172 | switch (dataType) { |
173 | case QVariant::Quaternion: |
174 | return channelComponentsToIndicesHelper(channelGroup: channel, expectedComponentCount, |
175 | offset, suffixes: quaternionSuffixes); |
176 | case QVariant::Color: |
177 | if (expectedComponentCount == 3) |
178 | return channelComponentsToIndicesHelper(channelGroup: channel, expectedComponentCount, |
179 | offset, suffixes: colorSuffixesRGB); |
180 | Q_ASSERT(expectedComponentCount == 4); |
181 | return channelComponentsToIndicesHelper(channelGroup: channel, expectedComponentCount, |
182 | offset, suffixes: colorSuffixesRGBA); |
183 | default: |
184 | return channelComponentsToIndicesHelper(channelGroup: channel, expectedComponentCount, |
185 | offset, suffixes: standardSuffixes); |
186 | } |
187 | } |
188 | |
189 | ComponentIndices channelComponentsToIndicesHelper(const Channel &channel, |
190 | int expectedComponentCount, |
191 | int offset, |
192 | const QVector<char> &suffixes) |
193 | { |
194 | const int actualComponentCount = channel.channelComponents.size(); |
195 | if (actualComponentCount != expectedComponentCount) { |
196 | qWarning() << "Data type expects" << expectedComponentCount |
197 | << "but found" << actualComponentCount << "components in the animation clip" ; |
198 | } |
199 | |
200 | ComponentIndices indices(expectedComponentCount); |
201 | |
202 | // Generate the set of channel suffixes |
203 | QVector<char> channelSuffixes; |
204 | channelSuffixes.reserve(asize: expectedComponentCount); |
205 | for (int i = 0; i < expectedComponentCount; ++i) { |
206 | const QString &componentName = channel.channelComponents[i].name; |
207 | |
208 | // An unset component name indicates that the no mapping is necessary |
209 | // and the index can be used as-is. |
210 | if (componentName.isEmpty()) { |
211 | indices[i] = i + offset; |
212 | continue; |
213 | } |
214 | |
215 | char channelSuffix = componentName.at(i: componentName.length() - 1).toLatin1(); |
216 | channelSuffixes.push_back(t: channelSuffix); |
217 | } |
218 | |
219 | // We can short-circuit if the channels were all unnamed (in order) |
220 | if (channelSuffixes.isEmpty()) |
221 | return indices; |
222 | |
223 | // Find index of standard index in channel indexes |
224 | for (int i = 0; i < expectedComponentCount; ++i) { |
225 | int index = channelSuffixes.indexOf(t: suffixes[i]); |
226 | if (index != -1) |
227 | indices[i] = index + offset; |
228 | else |
229 | indices[i] = -1; |
230 | } |
231 | |
232 | return indices; |
233 | } |
234 | |
235 | ClipResults evaluateClipAtLocalTime(AnimationClip *clip, float localTime) |
236 | { |
237 | QVector<float> channelResults; |
238 | Q_ASSERT(clip); |
239 | |
240 | // Ensure we have enough storage to hold the evaluations |
241 | channelResults.resize(asize: clip->channelCount()); |
242 | |
243 | // Iterate over channels and evaluate the fcurves |
244 | const QVector<Channel> &channels = clip->channels(); |
245 | int i = 0; |
246 | for (const Channel &channel : channels) { |
247 | if (channel.name.contains(QStringLiteral("Rotation" )) && |
248 | channel.channelComponents.size() == 4) { |
249 | |
250 | // Try to SLERP |
251 | const int nbKeyframes = channel.channelComponents[0].fcurve.keyframeCount(); |
252 | const bool canSlerp = std::find_if(first: std::begin(cont: channel.channelComponents)+1, |
253 | last: std::end(cont: channel.channelComponents), |
254 | pred: [nbKeyframes](const ChannelComponent &v) { |
255 | return v.fcurve.keyframeCount() != nbKeyframes; |
256 | }) == std::end(cont: channel.channelComponents); |
257 | |
258 | if (!canSlerp) { |
259 | // Interpolate per component |
260 | for (const auto &channelComponent : qAsConst(t: channel.channelComponents)) { |
261 | const int lowerKeyframeBound = channelComponent.fcurve.lowerKeyframeBound(localTime); |
262 | channelResults[i++] = channelComponent.fcurve.evaluateAtTime(localTime, lowerBound: lowerKeyframeBound); |
263 | } |
264 | } else { |
265 | // There's only one keyframe. We cant compute omega. Interpolate per component |
266 | if (channel.channelComponents[0].fcurve.keyframeCount() == 1) { |
267 | for (const auto &channelComponent : qAsConst(t: channel.channelComponents)) |
268 | channelResults[i++] = channelComponent.fcurve.keyframe(index: 0).value; |
269 | } else { |
270 | auto quaternionFromChannel = [channel](const int keyframe) { |
271 | const float w = channel.channelComponents[0].fcurve.keyframe(index: keyframe).value; |
272 | const float x = channel.channelComponents[1].fcurve.keyframe(index: keyframe).value; |
273 | const float y = channel.channelComponents[2].fcurve.keyframe(index: keyframe).value; |
274 | const float z = channel.channelComponents[3].fcurve.keyframe(index: keyframe).value; |
275 | QQuaternion quat{w,x,y,z}; |
276 | quat.normalize(); |
277 | return quat; |
278 | }; |
279 | |
280 | const int lowerKeyframeBound = std::max(a: 0, b: channel.channelComponents[0].fcurve.lowerKeyframeBound(localTime)); |
281 | const auto lowerQuat = quaternionFromChannel(lowerKeyframeBound); |
282 | const auto higherQuat = quaternionFromChannel(lowerKeyframeBound + 1); |
283 | auto cosHalfTheta = QQuaternion::dotProduct(q1: lowerQuat, q2: higherQuat); |
284 | // If the two keyframe quaternions are equal, just return the first one as the interpolated value. |
285 | if (std::abs(x: cosHalfTheta) >= 1.0f) { |
286 | channelResults[i++] = lowerQuat.scalar(); |
287 | channelResults[i++] = lowerQuat.x(); |
288 | channelResults[i++] = lowerQuat.y(); |
289 | channelResults[i++] = lowerQuat.z(); |
290 | } else { |
291 | const auto sinHalfTheta = std::sqrt(x: 1.0f - std::pow(x: cosHalfTheta,y: 2.0f)); |
292 | if (std::abs(x: sinHalfTheta) < ::slerpThreshold) { |
293 | auto initial_i = i; |
294 | for (const auto &channelComponent : qAsConst(t: channel.channelComponents)) |
295 | channelResults[i++] = channelComponent.fcurve.evaluateAtTime(localTime, lowerBound: lowerKeyframeBound); |
296 | |
297 | // Normalize the resulting quaternion |
298 | QQuaternion quat{channelResults[initial_i], channelResults[initial_i+1], channelResults[initial_i+2], channelResults[initial_i+3]}; |
299 | quat.normalize(); |
300 | channelResults[initial_i+0] = quat.scalar(); |
301 | channelResults[initial_i+1] = quat.x(); |
302 | channelResults[initial_i+2] = quat.y(); |
303 | channelResults[initial_i+3] = quat.z(); |
304 | } else { |
305 | const auto reverseQ1 = cosHalfTheta < 0 ? -1.0f : 1.0f; |
306 | cosHalfTheta *= reverseQ1; |
307 | const auto halfTheta = std::acos(x: cosHalfTheta); |
308 | for (const auto &channelComponent : qAsConst(t: channel.channelComponents)) |
309 | channelResults[i++] = channelComponent.fcurve.evaluateAtTimeAsSlerp(localTime, |
310 | lowerBound: lowerKeyframeBound, |
311 | halfTheta, |
312 | sinHalfTheta, |
313 | reverseQ1); |
314 | } |
315 | } |
316 | } |
317 | } |
318 | } else { |
319 | // If the channel is not a Rotation, apply linear interpolation per channel component |
320 | // TODO How do we handle other interpolations. For exammple, color interpolation |
321 | // in a linear perceptual way or other non linear spaces? |
322 | for (const auto &channelComponent : qAsConst(t: channel.channelComponents)) { |
323 | const int lowerKeyframeBound = channelComponent.fcurve.lowerKeyframeBound(localTime); |
324 | channelResults[i++] = channelComponent.fcurve.evaluateAtTime(localTime, lowerBound: lowerKeyframeBound); |
325 | } |
326 | } |
327 | } |
328 | return channelResults; |
329 | } |
330 | |
331 | ClipResults evaluateClipAtPhase(AnimationClip *clip, float phase) |
332 | { |
333 | // Calculate the clip local time from the phase and clip duration |
334 | const double localTime = phase * clip->duration(); |
335 | return evaluateClipAtLocalTime(clip, localTime); |
336 | } |
337 | |
338 | template<typename Container> |
339 | Container mapChannelResultsToContainer(const MappingData &mappingData, |
340 | const QVector<float> &channelResults) |
341 | { |
342 | Container r; |
343 | r.reserve(channelResults.size()); |
344 | |
345 | const ComponentIndices channelIndices = mappingData.channelIndices; |
346 | for (const int channelIndex : channelIndices) |
347 | r.push_back(channelResults.at(i: channelIndex)); |
348 | |
349 | return r; |
350 | } |
351 | |
352 | QVariant buildPropertyValue(const MappingData &mappingData, const QVector<float> &channelResults) |
353 | { |
354 | const int vectorOfFloatType = qMetaTypeId<QVector<float>>(); |
355 | |
356 | if (mappingData.type == vectorOfFloatType) |
357 | return QVariant::fromValue(value: channelResults); |
358 | |
359 | switch (mappingData.type) { |
360 | case QMetaType::Float: |
361 | case QVariant::Double: { |
362 | return QVariant::fromValue(value: channelResults[mappingData.channelIndices[0]]); |
363 | } |
364 | |
365 | case QVariant::Vector2D: { |
366 | const QVector2D vector(channelResults[mappingData.channelIndices[0]], |
367 | channelResults[mappingData.channelIndices[1]]); |
368 | return QVariant::fromValue(value: vector); |
369 | } |
370 | |
371 | case QVariant::Vector3D: { |
372 | const QVector3D vector(channelResults[mappingData.channelIndices[0]], |
373 | channelResults[mappingData.channelIndices[1]], |
374 | channelResults[mappingData.channelIndices[2]]); |
375 | return QVariant::fromValue(value: vector); |
376 | } |
377 | |
378 | case QVariant::Vector4D: { |
379 | const QVector4D vector(channelResults[mappingData.channelIndices[0]], |
380 | channelResults[mappingData.channelIndices[1]], |
381 | channelResults[mappingData.channelIndices[2]], |
382 | channelResults[mappingData.channelIndices[3]]); |
383 | return QVariant::fromValue(value: vector); |
384 | } |
385 | |
386 | case QVariant::Quaternion: { |
387 | QQuaternion q(channelResults[mappingData.channelIndices[0]], |
388 | channelResults[mappingData.channelIndices[1]], |
389 | channelResults[mappingData.channelIndices[2]], |
390 | channelResults[mappingData.channelIndices[3]]); |
391 | q.normalize(); |
392 | return QVariant::fromValue(value: q); |
393 | } |
394 | |
395 | case QVariant::Color: { |
396 | // A color can either be a vec3 or a vec4 |
397 | const QColor color = |
398 | QColor::fromRgbF(r: channelResults[mappingData.channelIndices[0]], |
399 | g: channelResults[mappingData.channelIndices[1]], |
400 | b: channelResults[mappingData.channelIndices[2]], |
401 | a: mappingData.channelIndices.size() > 3 ? channelResults[mappingData.channelIndices[3]] : 1.0f); |
402 | return QVariant::fromValue(value: color); |
403 | } |
404 | |
405 | case QVariant::List: { |
406 | const QVariantList results = mapChannelResultsToContainer<QVariantList>( |
407 | mappingData, channelResults); |
408 | return QVariant::fromValue(value: results); |
409 | } |
410 | default: |
411 | qWarning() << "Unhandled animation type" << mappingData.type; |
412 | break; |
413 | } |
414 | |
415 | return QVariant(); |
416 | } |
417 | |
418 | AnimationRecord prepareAnimationRecord(Qt3DCore::QNodeId animatorId, |
419 | const QVector<MappingData> &mappingDataVec, |
420 | const QVector<float> &channelResults, |
421 | bool finalFrame, |
422 | float normalizedLocalTime) |
423 | { |
424 | AnimationRecord record; |
425 | record.finalFrame = finalFrame; |
426 | record.animatorId = animatorId; |
427 | record.normalizedTime = normalizedLocalTime; |
428 | |
429 | QVarLengthArray<Skeleton *, 4> dirtySkeletons; |
430 | |
431 | // Iterate over the mappings |
432 | for (const MappingData &mappingData : mappingDataVec) { |
433 | if (!mappingData.propertyName) |
434 | continue; |
435 | |
436 | // Build the new value from the channel/fcurve evaluation results |
437 | const QVariant v = buildPropertyValue(mappingData, channelResults); |
438 | if (!v.isValid()) |
439 | continue; |
440 | |
441 | // TODO: Avoid wrapping joint transform components up in a variant, just |
442 | // to immediately unwrap them again. Refactor buildPropertyValue() to call |
443 | // helper functions that we can call directly here for joints. |
444 | if (mappingData.skeleton && mappingData.jointIndex != -1) { |
445 | // Remember that this skeleton is dirty. We will ask each dirty skeleton |
446 | // to send its set of local poses to observers below. |
447 | if (!dirtySkeletons.contains(t: mappingData.skeleton)) |
448 | dirtySkeletons.push_back(t: mappingData.skeleton); |
449 | |
450 | switch (mappingData.jointTransformComponent) { |
451 | case Scale: |
452 | mappingData.skeleton->setJointScale(jointIndex: mappingData.jointIndex, scale: v.value<QVector3D>()); |
453 | break; |
454 | |
455 | case Rotation: |
456 | mappingData.skeleton->setJointRotation(jointIndex: mappingData.jointIndex, rotation: v.value<QQuaternion>()); |
457 | break; |
458 | |
459 | case Translation: |
460 | mappingData.skeleton->setJointTranslation(jointIndex: mappingData.jointIndex, translation: v.value<QVector3D>()); |
461 | break; |
462 | |
463 | default: |
464 | Q_UNREACHABLE(); |
465 | break; |
466 | } |
467 | } else { |
468 | record.targetChanges.push_back(t: {mappingData.targetId, mappingData.propertyName, v}); |
469 | } |
470 | } |
471 | |
472 | for (const auto skeleton : dirtySkeletons) |
473 | record.skeletonChanges.push_back(t: {skeleton->peerId(), skeleton->joints()}); |
474 | |
475 | return record; |
476 | } |
477 | |
478 | QVector<AnimationCallbackAndValue> prepareCallbacks(const QVector<MappingData> &mappingDataVec, |
479 | const QVector<float> &channelResults) |
480 | { |
481 | QVector<AnimationCallbackAndValue> callbacks; |
482 | for (const MappingData &mappingData : mappingDataVec) { |
483 | if (!mappingData.callback) |
484 | continue; |
485 | const QVariant v = buildPropertyValue(mappingData, channelResults); |
486 | if (v.isValid()) { |
487 | AnimationCallbackAndValue callback; |
488 | callback.callback = mappingData.callback; |
489 | callback.flags = mappingData.callbackFlags; |
490 | callback.value = v; |
491 | callbacks.append(t: callback); |
492 | } |
493 | } |
494 | return callbacks; |
495 | } |
496 | |
497 | // TODO: Optimize this even more by combining the work done here with the functions: |
498 | // buildRequiredChannelsAndTypes() and assignChannelComponentIndices(). We are |
499 | // currently repeating the iteration over mappings and extracting/generating |
500 | // channel names, types and joint indices. |
501 | QVector<MappingData> buildPropertyMappings(const QVector<ChannelMapping*> &channelMappings, |
502 | const QVector<ChannelNameAndType> &channelNamesAndTypes, |
503 | const QVector<ComponentIndices> &channelComponentIndices, |
504 | const QVector<QBitArray> &sourceClipMask) |
505 | { |
506 | // Accumulate the required number of mappings |
507 | int maxMappingDatas = 0; |
508 | for (const auto mapping : channelMappings) { |
509 | switch (mapping->mappingType()) { |
510 | case ChannelMapping::ChannelMappingType: |
511 | case ChannelMapping::CallbackMappingType: |
512 | ++maxMappingDatas; |
513 | break; |
514 | |
515 | case ChannelMapping::SkeletonMappingType: { |
516 | Skeleton *skeleton = mapping->skeleton(); |
517 | maxMappingDatas += 3 * skeleton->jointCount(); // S, R, T |
518 | break; |
519 | } |
520 | } |
521 | } |
522 | QVector<MappingData> mappingDataVec; |
523 | mappingDataVec.reserve(asize: maxMappingDatas); |
524 | |
525 | // Iterate over the mappings |
526 | for (const auto mapping : channelMappings) { |
527 | switch (mapping->mappingType()) { |
528 | case ChannelMapping::ChannelMappingType: |
529 | case ChannelMapping::CallbackMappingType: { |
530 | // Populate the data we need, easy stuff first |
531 | MappingData mappingData; |
532 | mappingData.targetId = mapping->targetId(); |
533 | mappingData.propertyName = mapping->propertyName(); |
534 | mappingData.type = mapping->type(); |
535 | mappingData.callback = mapping->callback(); |
536 | mappingData.callbackFlags = mapping->callbackFlags(); |
537 | |
538 | if (mappingData.type == static_cast<int>(QVariant::Invalid)) { |
539 | qWarning() << "Unknown type for node id =" << mappingData.targetId |
540 | << "and property =" << mapping->propertyName() |
541 | << "and callback =" << mapping->callback(); |
542 | continue; |
543 | } |
544 | |
545 | // Try to find matching channel name and type |
546 | const ChannelNameAndType nameAndType = { mapping->channelName(), |
547 | mapping->type(), |
548 | mapping->componentCount(), |
549 | mapping->peerId() |
550 | }; |
551 | const int index = channelNamesAndTypes.indexOf(t: nameAndType); |
552 | if (index != -1) { |
553 | // Do we have any animation data for this channel? If not, don't bother |
554 | // adding a mapping for it. |
555 | const bool hasChannelIndices = sourceClipMask[index].count(on: true) != 0; |
556 | if (!hasChannelIndices) |
557 | continue; |
558 | |
559 | // We got one! |
560 | mappingData.channelIndices = channelComponentIndices[index]; |
561 | mappingDataVec.push_back(t: mappingData); |
562 | } |
563 | break; |
564 | } |
565 | |
566 | case ChannelMapping::SkeletonMappingType: { |
567 | const QVector<ChannelNameAndType> jointProperties |
568 | = { { QLatin1String("Location" ), static_cast<int>(QVariant::Vector3D), Translation }, |
569 | { QLatin1String("Rotation" ), static_cast<int>(QVariant::Quaternion), Rotation }, |
570 | { QLatin1String("Scale" ), static_cast<int>(QVariant::Vector3D), Scale } }; |
571 | const QHash<QString, const char *> channelNameToPropertyName |
572 | = { { QLatin1String("Location" ), "translation" }, |
573 | { QLatin1String("Rotation" ), "rotation" }, |
574 | { QLatin1String("Scale" ), "scale" } }; |
575 | Skeleton *skeleton = mapping->skeleton(); |
576 | const int jointCount = skeleton->jointCount(); |
577 | for (int jointIndex = 0; jointIndex < jointCount; ++jointIndex) { |
578 | // Populate the data we need, easy stuff first |
579 | MappingData mappingData; |
580 | mappingData.targetId = mapping->skeletonId(); |
581 | mappingData.skeleton = mapping->skeleton(); |
582 | |
583 | const int propertyCount = jointProperties.size(); |
584 | for (int propertyIndex = 0; propertyIndex < propertyCount; ++propertyIndex) { |
585 | // Get the name, type and index |
586 | ChannelNameAndType nameAndType = jointProperties[propertyIndex]; |
587 | nameAndType.jointIndex = jointIndex; |
588 | nameAndType.mappingId = mapping->peerId(); |
589 | |
590 | // Try to find matching channel name and type |
591 | const int index = channelNamesAndTypes.indexOf(t: nameAndType); |
592 | if (index == -1) |
593 | continue; |
594 | |
595 | // Do we have any animation data for this channel? If not, don't bother |
596 | // adding a mapping for it. |
597 | const bool hasChannelIndices = sourceClipMask[index].count(on: true) != 0; |
598 | if (!hasChannelIndices) |
599 | continue; |
600 | |
601 | if (index != -1) { |
602 | // We got one! |
603 | mappingData.propertyName = channelNameToPropertyName[nameAndType.name]; |
604 | mappingData.type = nameAndType.type; |
605 | mappingData.channelIndices = channelComponentIndices[index]; |
606 | mappingData.jointIndex = jointIndex; |
607 | |
608 | // Convert property name for joint transform components to |
609 | // an enumerated type so we can avoid the string comparisons |
610 | // when sending the change events after evaluation. |
611 | // TODO: Replace this logic as we now do it in buildRequiredChannelsAndTypes() |
612 | if (qstrcmp(str1: mappingData.propertyName, str2: "scale" ) == 0) |
613 | mappingData.jointTransformComponent = Scale; |
614 | else if (qstrcmp(str1: mappingData.propertyName, str2: "rotation" ) == 0) |
615 | mappingData.jointTransformComponent = Rotation; |
616 | else if (qstrcmp(str1: mappingData.propertyName, str2: "translation" ) == 0) |
617 | mappingData.jointTransformComponent = Translation; |
618 | |
619 | mappingDataVec.push_back(t: mappingData); |
620 | } |
621 | } |
622 | } |
623 | break; |
624 | } |
625 | } |
626 | } |
627 | |
628 | return mappingDataVec; |
629 | } |
630 | |
631 | QVector<ChannelNameAndType> buildRequiredChannelsAndTypes(Handler *handler, |
632 | const ChannelMapper *mapper) |
633 | { |
634 | ChannelMappingManager *mappingManager = handler->channelMappingManager(); |
635 | const QVector<Qt3DCore::QNodeId> mappingIds = mapper->mappingIds(); |
636 | |
637 | // Reserve enough storage assuming each mapping is for a different channel. |
638 | // May be overkill but avoids potential for multiple allocations |
639 | QVector<ChannelNameAndType> namesAndTypes; |
640 | namesAndTypes.reserve(asize: mappingIds.size()); |
641 | |
642 | // Iterate through the mappings and add ones not already used by an earlier mapping. |
643 | // We could add them all then sort and remove duplicates. However, our approach has the |
644 | // advantage of keeping the blend tree format more consistent with the mapping |
645 | // orderings which will have better cache locality when generating events. |
646 | for (const Qt3DCore::QNodeId mappingId : mappingIds) { |
647 | // Get the mapping object |
648 | ChannelMapping *mapping = mappingManager->lookupResource(id: mappingId); |
649 | Q_ASSERT(mapping); |
650 | |
651 | switch (mapping->mappingType()) { |
652 | case ChannelMapping::ChannelMappingType: |
653 | case ChannelMapping::CallbackMappingType: { |
654 | // Get the name and type |
655 | const ChannelNameAndType nameAndType{ mapping->channelName(), |
656 | mapping->type(), |
657 | mapping->componentCount(), |
658 | mappingId }; |
659 | |
660 | // Add if not already contained |
661 | if (!namesAndTypes.contains(t: nameAndType)) |
662 | namesAndTypes.push_back(t: nameAndType); |
663 | |
664 | break; |
665 | } |
666 | |
667 | case ChannelMapping::SkeletonMappingType: { |
668 | // Add an entry for each scale/rotation/translation property of each joint index |
669 | // of the target skeleton. |
670 | const QVector<ChannelNameAndType> jointProperties |
671 | = { { QLatin1String("Location" ), static_cast<int>(QVariant::Vector3D), Translation }, |
672 | { QLatin1String("Rotation" ), static_cast<int>(QVariant::Quaternion), Rotation }, |
673 | { QLatin1String("Scale" ), static_cast<int>(QVariant::Vector3D), Scale } }; |
674 | Skeleton *skeleton = handler->skeletonManager()->lookupResource(id: mapping->skeletonId()); |
675 | const int jointCount = skeleton->jointCount(); |
676 | for (int jointIndex = 0; jointIndex < jointCount; ++jointIndex) { |
677 | const int propertyCount = jointProperties.size(); |
678 | for (int propertyIndex = 0; propertyIndex < propertyCount; ++propertyIndex) { |
679 | // Get the name, type and index |
680 | ChannelNameAndType nameAndType = jointProperties[propertyIndex]; |
681 | nameAndType.jointName = skeleton->jointName(jointIndex); |
682 | nameAndType.jointIndex = jointIndex; |
683 | nameAndType.mappingId = mappingId; |
684 | |
685 | // Add if not already contained |
686 | if (!namesAndTypes.contains(t: nameAndType)) |
687 | namesAndTypes.push_back(t: nameAndType); |
688 | } |
689 | } |
690 | |
691 | break; |
692 | } |
693 | } |
694 | } |
695 | |
696 | return namesAndTypes; |
697 | } |
698 | |
699 | QVector<ComponentIndices> assignChannelComponentIndices(const QVector<ChannelNameAndType> &namesAndTypes) |
700 | { |
701 | QVector<ComponentIndices> channelComponentIndices; |
702 | channelComponentIndices.reserve(asize: namesAndTypes.size()); |
703 | |
704 | int baseIndex = 0; |
705 | for (const auto &entry : namesAndTypes) { |
706 | // Populate indices in order |
707 | const int componentCount = entry.componentCount; |
708 | ComponentIndices indices(componentCount); |
709 | std::iota(first: indices.begin(), last: indices.end(), value: baseIndex); |
710 | |
711 | // Append to the results |
712 | channelComponentIndices.push_back(t: indices); |
713 | |
714 | // Increment baseIndex |
715 | baseIndex += componentCount; |
716 | } |
717 | |
718 | return channelComponentIndices; |
719 | } |
720 | |
721 | QVector<Qt3DCore::QNodeId> gatherValueNodesToEvaluate(Handler *handler, |
722 | Qt3DCore::QNodeId blendTreeRootId) |
723 | { |
724 | Q_ASSERT(handler); |
725 | Q_ASSERT(blendTreeRootId.isNull() == false); |
726 | |
727 | // We need the ClipBlendNodeManager to be able to lookup nodes from their Ids |
728 | ClipBlendNodeManager *nodeManager = handler->clipBlendNodeManager(); |
729 | |
730 | // Visit the tree in a pre-order manner and collect the dependencies |
731 | QVector<Qt3DCore::QNodeId> clipIds; |
732 | ClipBlendNodeVisitor visitor(nodeManager, |
733 | ClipBlendNodeVisitor::PreOrder, |
734 | ClipBlendNodeVisitor::VisitOnlyDependencies); |
735 | |
736 | auto func = [&clipIds, nodeManager] (ClipBlendNode *blendNode) { |
737 | // Check if this is a value node itself |
738 | if (blendNode->blendType() == ClipBlendNode::ValueType) |
739 | clipIds.append(t: blendNode->peerId()); |
740 | |
741 | const auto dependencyIds = blendNode->currentDependencyIds(); |
742 | for (const auto dependencyId : dependencyIds) { |
743 | // Look up the blend node and if it's a value type (clip), |
744 | // add it to the set of value node ids that need to be evaluated |
745 | ClipBlendNode *node = nodeManager->lookupNode(id: dependencyId); |
746 | if (node && node->blendType() == ClipBlendNode::ValueType) |
747 | clipIds.append(t: dependencyId); |
748 | } |
749 | }; |
750 | visitor.traverse(rootId: blendTreeRootId, visitFunction: func); |
751 | |
752 | // Sort and remove duplicates |
753 | std::sort(first: clipIds.begin(), last: clipIds.end()); |
754 | auto last = std::unique(first: clipIds.begin(), last: clipIds.end()); |
755 | clipIds.erase(abegin: last, aend: clipIds.end()); |
756 | return clipIds; |
757 | } |
758 | |
759 | ClipFormat generateClipFormatIndices(const QVector<ChannelNameAndType> &targetChannels, |
760 | const QVector<ComponentIndices> &targetIndices, |
761 | const AnimationClip *clip) |
762 | { |
763 | Q_ASSERT(targetChannels.size() == targetIndices.size()); |
764 | |
765 | // Reserve enough storage for all the format indices |
766 | const int channelCount = targetChannels.size(); |
767 | ClipFormat f; |
768 | f.namesAndTypes.resize(asize: channelCount); |
769 | f.formattedComponentIndices.resize(asize: channelCount); |
770 | f.sourceClipMask.resize(asize: channelCount); |
771 | int indexCount = 0; |
772 | for (const auto &targetIndexVec : qAsConst(t: targetIndices)) |
773 | indexCount += targetIndexVec.size(); |
774 | ComponentIndices &sourceIndices = f.sourceClipIndices; |
775 | sourceIndices.resize(asize: indexCount); |
776 | |
777 | // Iterate through the target channels |
778 | auto formatIt = sourceIndices.begin(); |
779 | for (int i = 0; i < channelCount; ++i) { |
780 | // Find the index of the channel from the clip |
781 | const ChannelNameAndType &targetChannel = targetChannels[i]; |
782 | const int clipChannelIndex = clip->channelIndex(channelName: targetChannel.name, |
783 | jointIndex: targetChannel.jointIndex); |
784 | const int componentCount = targetIndices[i].size(); |
785 | |
786 | if (clipChannelIndex != -1) { |
787 | // Found a matching channel in the clip. Populate the corresponding |
788 | // entries in the format vector with the *source indices* |
789 | // needed to build the formatted results. |
790 | const int baseIndex = clip->channelComponentBaseIndex(channelGroupIndex: clipChannelIndex); |
791 | const auto channelIndices = channelComponentsToIndices(channel: clip->channels()[clipChannelIndex], |
792 | dataType: targetChannel.type, |
793 | expectedComponentCount: targetChannel.componentCount, |
794 | offset: baseIndex); |
795 | std::copy(first: channelIndices.begin(), last: channelIndices.end(), result: formatIt); |
796 | |
797 | f.sourceClipMask[i].resize(size: componentCount); |
798 | for (int j = 0; j < componentCount; ++j) |
799 | f.sourceClipMask[i].setBit(i: j, val: channelIndices[j] != -1); |
800 | } else { |
801 | // No such channel in this clip. We'll use default values when |
802 | // mapping from the clip to the formatted clip results. |
803 | std::fill(first: formatIt, last: formatIt + componentCount, value: -1); |
804 | f.sourceClipMask[i].fill(aval: false, asize: componentCount); |
805 | } |
806 | |
807 | f.formattedComponentIndices[i] = targetIndices[i]; |
808 | f.namesAndTypes[i] = targetChannels[i]; |
809 | formatIt += componentCount; |
810 | } |
811 | |
812 | return f; |
813 | } |
814 | |
815 | ClipResults formatClipResults(const ClipResults &rawClipResults, |
816 | const ComponentIndices &format) |
817 | { |
818 | // Resize the output to match the number of indices |
819 | const int elementCount = format.size(); |
820 | ClipResults formattedClipResults(elementCount); |
821 | |
822 | // Perform a gather operation to format the data |
823 | |
824 | // TODO: For large numbers of components do this in parallel with |
825 | // for e.g. a parallel_for() like construct |
826 | // TODO: We could potentially avoid having holes in these intermediate |
827 | // vectors by adjusting the component indices stored in the MappingData |
828 | // and format vectors. Needs careful investigation! |
829 | for (int i = 0; i < elementCount; ++i) { |
830 | if (format[i] == -1) |
831 | continue; |
832 | formattedClipResults[i] = rawClipResults[format[i]]; |
833 | } |
834 | |
835 | return formattedClipResults; |
836 | } |
837 | |
838 | ClipResults evaluateBlendTree(Handler *handler, |
839 | BlendedClipAnimator *animator, |
840 | Qt3DCore::QNodeId blendTreeRootId) |
841 | { |
842 | Q_ASSERT(handler); |
843 | Q_ASSERT(blendTreeRootId.isNull() == false); |
844 | const Qt3DCore::QNodeId animatorId = animator->peerId(); |
845 | |
846 | // We need the ClipBlendNodeManager to be able to lookup nodes from their Ids |
847 | ClipBlendNodeManager *nodeManager = handler->clipBlendNodeManager(); |
848 | |
849 | // Visit the tree in a post-order manner and for each interior node call |
850 | // blending function. We only need to visit the nodes that affect the blend |
851 | // tree at this time. |
852 | ClipBlendNodeVisitor visitor(nodeManager, |
853 | ClipBlendNodeVisitor::PostOrder, |
854 | ClipBlendNodeVisitor::VisitOnlyDependencies); |
855 | |
856 | // TODO: When jobs can spawn other jobs we could evaluate subtrees of |
857 | // the blend tree in parallel. Since it's just a dependency tree, it maps |
858 | // simply onto the dependencies between jobs. |
859 | auto func = [animatorId] (ClipBlendNode *blendNode) { |
860 | // Look up the blend node and if it's an interior node, perform |
861 | // the blend operation |
862 | if (blendNode->blendType() != ClipBlendNode::ValueType) |
863 | blendNode->blend(animatorId); |
864 | }; |
865 | visitor.traverse(rootId: blendTreeRootId, visitFunction: func); |
866 | |
867 | // The clip results stored in the root node for this animator |
868 | // now represent the result of the blend tree evaluation |
869 | ClipBlendNode *blendTreeRootNode = nodeManager->lookupNode(id: blendTreeRootId); |
870 | Q_ASSERT(blendTreeRootNode); |
871 | return blendTreeRootNode->clipResults(animatorId); |
872 | } |
873 | |
874 | QVector<float> defaultValueForChannel(Handler *handler, |
875 | const ChannelNameAndType &channelDescription) |
876 | { |
877 | QVector<float> result; |
878 | |
879 | // Does the channel repesent a joint in a skeleton or is it a general channel? |
880 | ChannelMappingManager *mappingManager = handler->channelMappingManager(); |
881 | const ChannelMapping *mapping = mappingManager->lookupResource(id: channelDescription.mappingId); |
882 | switch (mapping->mappingType()) { |
883 | case ChannelMapping::SkeletonMappingType: { |
884 | // Default channel values for a joint in a skeleton, should be taken |
885 | // from the default pose of the joint itself. I.e. if a joint is not |
886 | // explicitly animated, then it should retain it's initial rest pose. |
887 | Skeleton *skeleton = mapping->skeleton(); |
888 | const int jointIndex = channelDescription.jointIndex; |
889 | switch (channelDescription.jointTransformComponent) { |
890 | case Translation: |
891 | result = valueToVector(value: skeleton->jointTranslation(jointIndex)); |
892 | break; |
893 | |
894 | case Rotation: |
895 | result = valueToVector(value: skeleton->jointRotation(jointIndex)); |
896 | break; |
897 | |
898 | case Scale: |
899 | result = valueToVector(value: skeleton->jointScale(jointIndex)); |
900 | break; |
901 | |
902 | case NoTransformComponent: |
903 | Q_UNREACHABLE(); |
904 | break; |
905 | } |
906 | break; |
907 | } |
908 | |
909 | case ChannelMapping::ChannelMappingType: |
910 | case ChannelMapping::CallbackMappingType: { |
911 | // Do our best to provide a sensible default value. |
912 | if (channelDescription.type == QMetaType::QQuaternion) { |
913 | result = valueToVector(value: QQuaternion()); // (1, 0, 0, 0) |
914 | break; |
915 | } |
916 | |
917 | if (channelDescription.name.toLower() == QLatin1String("scale" )) { |
918 | result = valueToVector(value: QVector3D(1.0f, 1.0f, 1.0f)); |
919 | break; |
920 | } |
921 | |
922 | // Everything else gets all zeros |
923 | const int componentCount = mapping->componentCount(); |
924 | result = QVector<float>(componentCount, 0.0f); |
925 | break; |
926 | } |
927 | |
928 | } |
929 | |
930 | return result; |
931 | } |
932 | |
933 | void applyComponentDefaultValues(const QVector<ComponentValue> &componentDefaults, |
934 | ClipResults &formattedClipResults) |
935 | { |
936 | for (const auto &componentDefault : componentDefaults) |
937 | formattedClipResults[componentDefault.componentIndex] = componentDefault.value; |
938 | } |
939 | |
940 | } // Animation |
941 | } // Qt3DAnimation |
942 | |
943 | QT_END_NAMESPACE |
944 | |