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
54QT_BEGIN_NAMESPACE
55
56namespace {
57const auto slerpThreshold = 0.01f;
58}
59
60namespace Qt3DAnimation {
61namespace Animation {
62
63inline QVector<float> valueToVector(const QVector3D &value)
64{
65 return { value.x(), value.y(), value.z() };
66}
67
68inline QVector<float> valueToVector(const QQuaternion &value)
69{
70 return { value.scalar(), value.x(), value.y(), value.z() };
71}
72
73ClipEvaluationData 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
91double localTimeFromElapsedTime(double t_current_local,
92 double t_elapsed_global,
93 double playbackRate,
94 double duration,
95 int loopCount,
96 int &currentLoop)
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
132double phaseFromElapsedTime(double t_current_local,
133 double t_elapsed_global,
134 double playbackRate,
135 double duration,
136 int loopCount,
137 int &currentLoop)
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*/
155ComponentIndices 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
189ComponentIndices 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
235ClipResults 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
331ClipResults 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
338template<typename Container>
339Container 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
352QVariant 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
418AnimationRecord 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
478QVector<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.
501QVector<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
631QVector<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
699QVector<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
721QVector<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
759ClipFormat 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
815ClipResults 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
838ClipResults 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
874QVector<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
933void 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
943QT_END_NAMESPACE
944

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