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 | |
7 | QT_BEGIN_NAMESPACE |
8 | |
9 | namespace 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 | |
134 | QMorphingAnimationPrivate::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 | |
147 | QMorphingAnimationPrivate::~QMorphingAnimationPrivate() |
148 | { |
149 | for (QVector<float> *weights : std::as_const(t&: m_weights)) |
150 | delete weights; |
151 | } |
152 | |
153 | void 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 | |
215 | void 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 | */ |
244 | QMorphingAnimation::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 | |
252 | QVector<float> QMorphingAnimation::targetPositions() const |
253 | { |
254 | Q_D(const QMorphingAnimation); |
255 | return d->m_targetPositions; |
256 | } |
257 | |
258 | float QMorphingAnimation::interpolator() const |
259 | { |
260 | Q_D(const QMorphingAnimation); |
261 | return d->m_interpolator; |
262 | } |
263 | |
264 | Qt3DRender::QGeometryRenderer *QMorphingAnimation::target() const |
265 | { |
266 | Q_D(const QMorphingAnimation); |
267 | return d->m_target; |
268 | } |
269 | |
270 | QString QMorphingAnimation::targetName() const |
271 | { |
272 | Q_D(const QMorphingAnimation); |
273 | return d->m_targetName; |
274 | } |
275 | |
276 | QMorphingAnimation::Method QMorphingAnimation::method() const |
277 | { |
278 | Q_D(const QMorphingAnimation); |
279 | return d->m_method; |
280 | } |
281 | |
282 | QEasingCurve 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 | */ |
291 | void 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 | */ |
302 | void 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 | */ |
316 | void 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 | |
323 | void 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 | |
341 | void 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 | */ |
354 | void 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 | */ |
368 | QVector<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 | */ |
377 | QVector<QMorphTarget *> QMorphingAnimation::morphTargetList() |
378 | { |
379 | Q_D(QMorphingAnimation); |
380 | return d->m_morphTargets; |
381 | } |
382 | |
383 | void 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 | |
392 | void 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 | |
402 | void 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 | |
412 | void QMorphingAnimation::updateAnimation(float position) |
413 | { |
414 | Q_D(QMorphingAnimation); |
415 | d->updateAnimation(position); |
416 | } |
417 | |
418 | } // Qt3DAnimation |
419 | |
420 | QT_END_NAMESPACE |
421 | |
422 | #include "moc_qmorphinganimation.cpp" |
423 | |