1// Copyright (C) 2017 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qmorphinganimation.h"
5#include <private/qmorphinganimation_p.h>
6
7QT_BEGIN_NAMESPACE
8
9namespace Qt3DAnimation {
10
11/*!
12 \class Qt3DAnimation::QMorphingAnimation
13 \brief A class implementing blend-shape morphing animation.
14 \inmodule Qt3DAnimation
15 \since 5.9
16 \inherits Qt3DAnimation::QAbstractAnimation
17
18 A Qt3DAnimation::QMorphingAnimation class implements blend-shape morphing animation
19 to a target \l {Qt3DRender::QGeometryRenderer}{QGeometryRenderer}. The QMorphingAnimation
20 sets the correct \l {Qt3DCore::QAttribute}{QAttributes} from the
21 \l {Qt3DAnimation::QMorphTarget}{morph targets} to the target
22 \l {Qt3DRender::QGeometryRenderer::geometry} {QGeometryRenderer::geometry} and calculates
23 interpolator for the current position. The actual blending between the attributes must
24 be implemented in the material. Qt3DAnimation::QMorphPhongMaterial implements material
25 with morphing support for phong lighting model. The blending happens between
26 2 attributes - 'base' and 'target'. The names for the base and target attributes are taken from
27 the morph target names, where the base attribute retains the name it already has and the
28 target attribute name gets 'Target' appended to the name. The interpolator can be
29 set as a \l {Qt3DRender::QParameter}{QParameter} to the used material.
30 All morph targets in the animation should contain the attributes with same names as those
31 in the base geometry.
32
33*/
34/*!
35 \qmltype MorphingAnimation
36 \brief A type implementing blend-shape morphing animation.
37 \inqmlmodule Qt3D.Animation
38 \since 5.9
39 \inherits AbstractAnimation
40 \instantiates Qt3DAnimation::QMorphingAnimation
41
42 A MorphingAnimation type implements blend-shape morphing animation
43 to a target \l GeometryRenderer. The MorphingAnimation sets the correct
44 \l {Attribute}{Attributes} from the morph targets to the target
45 \l {Qt3D.Render::GeometryRenderer::geometry}{GeometryRenderer::geometry} and calculates
46 interpolator for the current position. The actual blending between the attributes must
47 be implemented in the material. MorphPhongMaterial implements material
48 with morphing support for phong lighting model. The blending happens between
49 2 attributes - 'base' and 'target'. The names for the base and target attributes are taken from
50 the morph target names, where the base attribute retains the name it already has and the
51 target attribute name gets 'Target' appended to the name. All morph targets in the animation
52 should contain the attributes with same names as those in the base geometry.
53
54*/
55/*!
56 \property Qt3DAnimation::QMorphingAnimation::targetPositions
57 Holds the position values of the morph target. Each position in the list specifies the position
58 of the corresponding morph target with the same index. The values must be in an ascending order.
59 Values can be positive or negative and do not have any predefined unit.
60*/
61/*!
62 \property Qt3DAnimation::QMorphingAnimation::interpolator
63 Holds the interpolator between base and target attributes.
64 \readonly
65*/
66/*!
67 \property Qt3DAnimation::QMorphingAnimation::target
68 Holds the target QGeometryRenderer the morphing animation is applied to.
69*/
70/*!
71 \property Qt3DAnimation::QMorphingAnimation::targetName
72 Holds the name of the target geometry. This is a convenience property making it
73 easier to match the target geometry to the morphing animation. The name
74 is usually same as the name of the parent entity of the target QGeometryRenderer, but
75 does not have to be.
76*/
77/*!
78 \property Qt3DAnimation::QMorphingAnimation::method
79 Holds the morphing method. The default is Relative.
80*/
81/*!
82 \property Qt3DAnimation::QMorphingAnimation::easing
83 Holds the easing curve of the interpolator between morph targets.
84*/
85/*!
86 \enum Qt3DAnimation::QMorphingAnimation::Method
87
88 This enumeration specifies the morphing method.
89 \value Normalized The blending should use the normalized formula;
90 V' = Vbase * (1.0 - sum(Wi)) + sum[Vi * Wi]
91 \value Relative The blending should use the relative formula;
92 V' = Vbase + sum[Vi * Wi]
93*/
94
95/*!
96 \qmlproperty list<real> MorphingAnimation::targetPositions
97 Holds the position values of the morph target. Each position in the list specifies the position
98 of the corresponding morph target with the same index. The values must be in an ascending order.
99 Values can be positive or negative and do not have any predefined unit.
100*/
101/*!
102 \qmlproperty real MorphingAnimation::interpolator
103 Holds the interpolator between base and target attributes.
104 \readonly
105*/
106/*!
107 \qmlproperty GeometryRenderer MorphingAnimation::target
108 Holds the target GeometryRenderer the morphing animation is applied to.
109*/
110/*!
111 \qmlproperty string MorphingAnimation::targetName
112 Holds the name of the target geometry. This is a convenience property making it
113 easier to match the target geometry to the morphing animation. The name
114 is usually same as the name of the parent entity of the target GeometryRenderer, but
115 does not have to be.
116*/
117/*!
118 \qmlproperty enumeration MorphingAnimation::method
119 Holds the morphing method. The default is Relative.
120 \list
121 \li Normalized
122 \li Relative
123 \endlist
124*/
125/*!
126 \qmlproperty EasingCurve MorphingAnimation::easing
127 Holds the easing curve of the interpolator between morph targets.
128*/
129/*!
130 \qmlproperty list<MorphTarget> MorphingAnimation::morphTargets
131 Holds the list of morph targets in the morphing animation.
132*/
133
134QMorphingAnimationPrivate::QMorphingAnimationPrivate()
135 : QAbstractAnimationPrivate(QAbstractAnimation::MorphingAnimation)
136 , m_minposition(0.0f)
137 , m_maxposition(0.0f)
138 , m_flattened(nullptr)
139 , m_method(QMorphingAnimation::Relative)
140 , m_interpolator(0.0f)
141 , m_target(nullptr)
142 , m_currentTarget(nullptr)
143{
144
145}
146
147QMorphingAnimationPrivate::~QMorphingAnimationPrivate()
148{
149 for (QVector<float> *weights : std::as_const(t&: m_weights))
150 delete weights;
151}
152
153void QMorphingAnimationPrivate::updateAnimation(float position)
154{
155 Q_Q(QMorphingAnimation);
156 if (!m_target || !m_target->geometry())
157 return;
158
159 QVector<int> relevantValues;
160 float sum = 0.0f;
161 float interpolator = 0.0f;
162 m_morphKey.resize(size: m_morphTargets.size());
163
164 // calculate morph key
165 if (position < m_minposition) {
166 m_morphKey = *m_weights.first();
167 } else if (position >= m_maxposition) {
168 m_morphKey = *m_weights.last();
169 } else {
170 for (int i = 0; i < m_targetPositions.size() - 1; ++i) {
171 if (position >= m_targetPositions.at(i) && position < m_targetPositions.at(i: i + 1)) {
172 interpolator = (position - m_targetPositions.at(i))
173 / (m_targetPositions.at(i: i + 1) - m_targetPositions.at(i));
174 interpolator = m_easing.valueForProgress(progress: interpolator);
175 float iip = 1.0f - interpolator;
176
177 for (int j = 0; j < m_morphTargets.size(); ++j) {
178 m_morphKey[j] = interpolator * m_weights.at(i: i + 1)->at(i: j)
179 + iip * m_weights.at(i)->at(i: j);
180 }
181 }
182 }
183 }
184
185 // check relevant values
186 for (int j = 0; j < m_morphKey.size(); ++j) {
187 sum += m_morphKey[j];
188 if (!qFuzzyIsNull(f: m_morphKey[j]))
189 relevantValues.push_back(t: j);
190 }
191
192 if (relevantValues.size() == 0 || qFuzzyIsNull(f: sum)) {
193 // only base is used
194 interpolator = 0.0f;
195 } else if (relevantValues.size() == 1) {
196 // one morph target has non-zero weight
197 setTargetInterpolated(relevantValues[0]);
198 interpolator = sum;
199 } else {
200 // more than one morph target has non-zero weight
201 // flatten morph targets to one
202 qWarning() << Q_FUNC_INFO << "Flattening required";
203 }
204
205 // Relative method uses negative interpolator, normalized uses positive
206 if (m_method == QMorphingAnimation::Relative)
207 interpolator = -interpolator;
208
209 if (!qFuzzyCompare(p1: interpolator, p2: m_interpolator)) {
210 m_interpolator = interpolator;
211 emit q->interpolatorChanged(interpolator: m_interpolator);
212 }
213}
214
215void QMorphingAnimationPrivate::setTargetInterpolated(int morphTarget)
216{
217 QMorphTarget *target = m_morphTargets[morphTarget];
218 Qt3DCore::QGeometry *geometry = m_target->geometry();
219
220 // remove attributes from previous frame
221 if (m_currentTarget && (target != m_currentTarget)) {
222 const QList<Qt3DCore::QAttribute *> targetAttributes = m_currentTarget->attributeList();
223 for (int i = 0; i < targetAttributes.size(); ++i)
224 geometry->removeAttribute(attribute: targetAttributes.at(i));
225 }
226
227 const QList<Qt3DCore::QAttribute *> targetAttributes = target->attributeList();
228
229 // add attributes from current frame to the geometry
230 if (target != m_currentTarget) {
231 for (int i = 0; i < m_attributeNames.size(); ++i) {
232 QString targetName = m_attributeNames.at(i);
233 targetName.append(s: QLatin1String("Target"));
234 targetAttributes[i]->setName(targetName);
235 geometry->addAttribute(attribute: targetAttributes.at(i));
236 }
237 }
238 m_currentTarget = target;
239}
240
241/*!
242 Construct a new QMorphingAnimation with \a parent.
243 */
244QMorphingAnimation::QMorphingAnimation(QObject *parent)
245 : QAbstractAnimation(*new QMorphingAnimationPrivate, parent)
246{
247 Q_D(QMorphingAnimation);
248 d->m_positionConnection = QObject::connect(sender: this, signal: &QAbstractAnimation::positionChanged,
249 context: this, slot: &QMorphingAnimation::updateAnimation);
250}
251
252QVector<float> QMorphingAnimation::targetPositions() const
253{
254 Q_D(const QMorphingAnimation);
255 return d->m_targetPositions;
256}
257
258float QMorphingAnimation::interpolator() const
259{
260 Q_D(const QMorphingAnimation);
261 return d->m_interpolator;
262}
263
264Qt3DRender::QGeometryRenderer *QMorphingAnimation::target() const
265{
266 Q_D(const QMorphingAnimation);
267 return d->m_target;
268}
269
270QString QMorphingAnimation::targetName() const
271{
272 Q_D(const QMorphingAnimation);
273 return d->m_targetName;
274}
275
276QMorphingAnimation::Method QMorphingAnimation::method() const
277{
278 Q_D(const QMorphingAnimation);
279 return d->m_method;
280}
281
282QEasingCurve QMorphingAnimation::easing() const
283{
284 Q_D(const QMorphingAnimation);
285 return d->m_easing;
286}
287
288/*!
289 Set morph \a targets to animation. Old targets are cleared.
290*/
291void QMorphingAnimation::setMorphTargets(const QVector<Qt3DAnimation::QMorphTarget *> &targets)
292{
293 Q_D(QMorphingAnimation);
294 d->m_morphTargets = targets;
295 d->m_attributeNames = targets[0]->attributeNames();
296 d->m_position = -1.0f;
297}
298
299/*!
300 Add new morph \a target at the end of the animation.
301*/
302void QMorphingAnimation::addMorphTarget(Qt3DAnimation::QMorphTarget *target)
303{
304 Q_D(QMorphingAnimation);
305 if (!d->m_morphTargets.contains(t: target)) {
306 d->m_morphTargets.push_back(t: target);
307 d->m_position = -1.0f;
308 if (d->m_attributeNames.empty())
309 d->m_attributeNames = target->attributeNames();
310 }
311}
312
313/*!
314 Remove morph \a target from the animation.
315*/
316void QMorphingAnimation::removeMorphTarget(Qt3DAnimation::QMorphTarget *target)
317{
318 Q_D(QMorphingAnimation);
319 d->m_morphTargets.removeAll(t: target);
320 d->m_position = -1.0f;
321}
322
323void QMorphingAnimation::setTargetPositions(const QVector<float> &targetPositions)
324{
325 Q_D(QMorphingAnimation);
326 d->m_targetPositions = targetPositions;
327 emit targetPositionsChanged(targetPositions);
328 d->m_minposition = targetPositions.first();
329 d->m_maxposition = targetPositions.last();
330 setDuration(d->m_targetPositions.last());
331 if (d->m_weights.size() < targetPositions.size()) {
332 d->m_weights.resize(size: targetPositions.size());
333 for (int i = 0; i < d->m_weights.size(); ++i) {
334 if (d->m_weights[i] == nullptr)
335 d->m_weights[i] = new QVector<float>();
336 }
337 }
338 d->m_position = -1.0f;
339}
340
341void QMorphingAnimation::setTarget(Qt3DRender::QGeometryRenderer *target)
342{
343 Q_D(QMorphingAnimation);
344 if (d->m_target != target) {
345 d->m_position = -1.0f;
346 d->m_target = target;
347 emit targetChanged(target);
348 }
349}
350
351/*!
352 Sets morph \a weights at \a positionIndex.
353*/
354void QMorphingAnimation::setWeights(int positionIndex, const QVector<float> &weights)
355{
356 Q_D(QMorphingAnimation);
357 if (d->m_weights.size() < positionIndex)
358 d->m_weights.resize(size: positionIndex + 1);
359 if (d->m_weights[positionIndex] == nullptr)
360 d->m_weights[positionIndex] = new QVector<float>();
361 *d->m_weights[positionIndex] = weights;
362 d->m_position = -1.0f;
363}
364
365/*!
366 Return morph weights at \a positionIndex.
367*/
368QVector<float> QMorphingAnimation::getWeights(int positionIndex)
369{
370 Q_D(QMorphingAnimation);
371 return *d->m_weights[positionIndex];
372}
373
374/*!
375 Return morph target list.
376*/
377QVector<QMorphTarget *> QMorphingAnimation::morphTargetList()
378{
379 Q_D(QMorphingAnimation);
380 return d->m_morphTargets;
381}
382
383void QMorphingAnimation::setTargetName(const QString name)
384{
385 Q_D(QMorphingAnimation);
386 if (d->m_targetName != name) {
387 d->m_targetName = name;
388 emit targetNameChanged(name);
389 }
390}
391
392void QMorphingAnimation::setMethod(QMorphingAnimation::Method method)
393{
394 Q_D(QMorphingAnimation);
395 if (d->m_method != method) {
396 d->m_method = method;
397 d->m_position = -1.0f;
398 emit methodChanged(method);
399 }
400}
401
402void QMorphingAnimation::setEasing(const QEasingCurve &easing)
403{
404 Q_D(QMorphingAnimation);
405 if (d->m_easing != easing) {
406 d->m_easing = easing;
407 d->m_position = -1.0f;
408 emit easingChanged(easing);
409 }
410}
411
412void QMorphingAnimation::updateAnimation(float position)
413{
414 Q_D(QMorphingAnimation);
415 d->updateAnimation(position);
416}
417
418} // Qt3DAnimation
419
420QT_END_NAMESPACE
421
422#include "moc_qmorphinganimation.cpp"
423

source code of qt3d/src/animation/frontend/qmorphinganimation.cpp