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