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 | |
40 | QT_BEGIN_NAMESPACE |
41 | |
42 | namespace 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 | |
167 | QMorphingAnimationPrivate::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 | |
180 | QMorphingAnimationPrivate::~QMorphingAnimationPrivate() |
181 | { |
182 | for (QVector<float> *weights : qAsConst(t&: m_weights)) |
183 | delete weights; |
184 | } |
185 | |
186 | void 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(asize: 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: i + 1)) { |
205 | interpolator = (position - m_targetPositions.at(i)) |
206 | / (m_targetPositions.at(i: i + 1) - m_targetPositions.at(i)); |
207 | interpolator = m_easing.valueForProgress(progress: 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: i + 1)->at(i: j) |
212 | + iip * m_weights.at(i)->at(i: 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(f: m_morphKey[j])) |
222 | relevantValues.push_back(t: j); |
223 | } |
224 | |
225 | if (relevantValues.size() == 0 || qFuzzyIsNull(f: 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(p1: interpolator, p2: m_interpolator)) { |
243 | m_interpolator = interpolator; |
244 | emit q->interpolatorChanged(interpolator: m_interpolator); |
245 | } |
246 | } |
247 | |
248 | void 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(attribute: 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(s: QLatin1String("Target" )); |
267 | targetAttributes[i]->setName(targetName); |
268 | geometry->addAttribute(attribute: targetAttributes.at(i)); |
269 | } |
270 | } |
271 | m_currentTarget = target; |
272 | } |
273 | |
274 | /*! |
275 | Construct a new QMorphingAnimation with \a parent. |
276 | */ |
277 | QMorphingAnimation::QMorphingAnimation(QObject *parent) |
278 | : QAbstractAnimation(*new QMorphingAnimationPrivate, parent) |
279 | { |
280 | Q_D(QMorphingAnimation); |
281 | d->m_positionConnection = QObject::connect(sender: this, signal: &QAbstractAnimation::positionChanged, |
282 | receiver: this, slot: &QMorphingAnimation::updateAnimation); |
283 | } |
284 | |
285 | QVector<float> QMorphingAnimation::targetPositions() const |
286 | { |
287 | Q_D(const QMorphingAnimation); |
288 | return d->m_targetPositions; |
289 | } |
290 | |
291 | float QMorphingAnimation::interpolator() const |
292 | { |
293 | Q_D(const QMorphingAnimation); |
294 | return d->m_interpolator; |
295 | } |
296 | |
297 | Qt3DRender::QGeometryRenderer *QMorphingAnimation::target() const |
298 | { |
299 | Q_D(const QMorphingAnimation); |
300 | return d->m_target; |
301 | } |
302 | |
303 | QString QMorphingAnimation::targetName() const |
304 | { |
305 | Q_D(const QMorphingAnimation); |
306 | return d->m_targetName; |
307 | } |
308 | |
309 | QMorphingAnimation::Method QMorphingAnimation::method() const |
310 | { |
311 | Q_D(const QMorphingAnimation); |
312 | return d->m_method; |
313 | } |
314 | |
315 | QEasingCurve 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 | */ |
324 | void 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 | */ |
335 | void QMorphingAnimation::addMorphTarget(Qt3DAnimation::QMorphTarget *target) |
336 | { |
337 | Q_D(QMorphingAnimation); |
338 | if (!d->m_morphTargets.contains(t: target)) { |
339 | d->m_morphTargets.push_back(t: 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 | */ |
349 | void QMorphingAnimation::removeMorphTarget(Qt3DAnimation::QMorphTarget *target) |
350 | { |
351 | Q_D(QMorphingAnimation); |
352 | d->m_morphTargets.removeAll(t: target); |
353 | d->m_position = -1.0f; |
354 | } |
355 | |
356 | void 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(asize: 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 | |
374 | void 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 | */ |
387 | void 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(asize: 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 | */ |
401 | QVector<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 | */ |
410 | QVector<Qt3DAnimation::QMorphTarget *> QMorphingAnimation::morphTargetList() |
411 | { |
412 | Q_D(QMorphingAnimation); |
413 | return d->m_morphTargets; |
414 | } |
415 | |
416 | void 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 | |
425 | void 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 | |
435 | void 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 | |
445 | void QMorphingAnimation::updateAnimation(float position) |
446 | { |
447 | Q_D(QMorphingAnimation); |
448 | d->updateAnimation(position); |
449 | } |
450 | |
451 | } // Qt3DAnimation |
452 | |
453 | QT_END_NAMESPACE |
454 | |